Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I have always been skeptical about 'scientific' languages. Why do you need a special language when any general language + some libraries will do? This is a good example of a feature that only really makes sense in a scientific language.


Numerical programming takes more expressive power than one might imagine. In most languages, numerical primitives like integers and floating-point numbers, and numerical operators like `+` and `[]` (array indexing), are very special and are endowed with enough magic to be usable. E.g. in C, `+` is too polymorphic to be defined as a function; in Python, `+` has special `__radd__` methods to (hackily) emulate multiple dispatch; in Java `int`, `float` and `double` are entirely different kinds of values (non-objects) from normal user-definable objects. In many ways, the fundamental premise of Julia is to design a language with sufficient power and performance that numbers are not special: "primitive" types like `Int`, `Float64` are just defined in normal Julia code, and operators like `+` and `[]` are normal Julia functions like any other.

This recent post gives some more motivation: https://discourse.julialang.org/t/julia-motivation-why-weren....


I like this quote from the 'motivation' link above:

"A lot of Julia's design fell directly out of making it easy to generate LLVM IR – in some ways, Julia is just a really great DSL for LLVM's JIT."

Excited to learn more


Julia is my language of choice for writing compilers.

Quasiquoting means that codegen is as easy as string interpolation is in other languages.

    quote 
      let
        $(index_inits...)
        $(results_inits...)
        if $(reduce((a,b) -> :($a && $b), true, index_checks))
          let 
            $(var_inits...) # declare vars local in here so they can't shadow relation names
            $body 
          end
        end
        tuple($(results...))
      end
    end
Great introspection into the inference and compilation pipeline, directly from the repl.

    julia> function double(xs)
             [2*x for x in xs]
           end
    double (generic function with 1 method)

    julia> double([1,2,3])
    3-element Array{Int64,1}:
     2
     4
     6

    julia> @code_warntype double([1,2,3])
    Variables:
      xs::Array{Int64,1}
      #s1::Int64
      #s2::Int64
      #s3::Int64
      x::Int64
      #s4::Int64

    Body:
      begin  # none, line 2:
          GenSym(1) = (Base.arraylen)(xs::Array{Int64,1})::Int64
          0: 
          GenSym(3) = (top(ccall))(:jl_alloc_array_1d,(top(apply_type))(Base.Array,Int64,1)::Type{Array{Int64,1}},(top(svec))(Base.Any,Base.Int)::SimpleVector,Array{Int64,1},0,GenSym(1),0)::Array{Int64,1}
          #s1 = 1
          #s2 = 1
          #s3 = 0
          unless (Base.box)(Base.Bool,(Base.not_int)(#s3::Int64 === GenSym(1)::Bool)) goto 2
          3: 
          #s3 = (Base.box)(Base.Int,(Base.add_int)(#s3::Int64,1))
          GenSym(9) = (Base.arrayref)(xs::Array{Int64,1},#s2::Int64)::Int64
          GenSym(10) = (Base.box)(Base.Int,(Base.add_int)(#s2::Int64,1))
          #s4 = 1
          GenSym(11) = GenSym(9)
          GenSym(12) = (Base.box)(Base.Int,(Base.add_int)(1,1))
          x = GenSym(11)
          #s4 = GenSym(12)
          GenSym(13) = GenSym(10)
          GenSym(14) = (Base.box)(Base.Int,(Base.add_int)(2,1))
          #s2 = GenSym(13)
          #s4 = GenSym(14)
          GenSym(4) = (Base.box)(Int64,(Base.mul_int)(2,x::Int64))
          $(Expr(:type_goto, 0, GenSym(4)))
          $(Expr(:boundscheck, false))
          (Base.arrayset)(GenSym(3),GenSym(4),#s1::Int64)::Array{Int64,1}
          $(Expr(:boundscheck, :(Main.pop)))
          #s1 = (Base.box)(Base.Int,(Base.add_int)(#s1::Int64,1))
          4: 
          unless (Base.box)(Base.Bool,(Base.not_int)((Base.box)(Base.Bool,(Base.not_int)(#s3::Int64 === GenSym(1)::Bool)))) goto 3
          2: 
          1: 
          return GenSym(3)
      end::Array{Int64,1}

    julia> @code_llvm double([1,2,3])

    define %jl_value_t* @julia_double_21481(%jl_value_t*, %jl_value_t**, i32) {
    top:
      %3 = alloca [4 x %jl_value_t*], align 8
      %.sub = getelementptr inbounds [4 x %jl_value_t*], [4 x %jl_value_t*]* %3, i64 0, i64 0
      %4 = getelementptr [4 x %jl_value_t*], [4 x %jl_value_t*]* %3, i64 0, i64 2
      %5 = getelementptr [4 x %jl_value_t*], [4 x %jl_value_t*]* %3, i64 0, i64 3
      %6 = bitcast [4 x %jl_value_t*]* %3 to i64*
      store i64 4, i64* %6, align 8
      %7 = getelementptr [4 x %jl_value_t*], [4 x %jl_value_t*]* %3, i64 0, i64 1
      %8 = load i64, i64* bitcast (%jl_value_t*** @jl_pgcstack to i64*), align 8
      %9 = bitcast %jl_value_t** %7 to i64*
      store i64 %8, i64* %9, align 8
      store %jl_value_t** %.sub, %jl_value_t*** @jl_pgcstack, align 8
      store %jl_value_t* null, %jl_value_t** %4, align 8
      store %jl_value_t* null, %jl_value_t** %5, align 8
      %10 = load %jl_value_t*, %jl_value_t** %1, align 8
      %11 = getelementptr inbounds %jl_value_t, %jl_value_t* %10, i64 1
      %12 = bitcast %jl_value_t* %11 to i64*
      %13 = load i64, i64* %12, align 8
      store %jl_value_t* inttoptr (i64 140140655870352 to %jl_value_t*), %jl_value_t** %5, align 8
      %14 = call %jl_value_t* inttoptr (i64 140149385306688 to %jl_value_t* (%jl_value_t*, i64)*)(%jl_value_t* inttoptr (i64 140140655870352 to %jl_value_t*), i64 inreg %13)
      store %jl_value_t* %14, %jl_value_t** %4, align 8
      %15 = icmp eq i64 %13, 0
      br i1 %15, label %L4, label %L1.preheader

    L1.preheader:                                     ; preds = %top
      %16 = load i64, i64* %12, align 8
      %17 = bitcast %jl_value_t* %10 to i64**
      %18 = bitcast %jl_value_t* %14 to i64**
      br label %L1

    L1:                                               ; preds = %idxend, %L1.preheader
      %"#s3.0" = phi i64 [ %22, %idxend ], [ 0, %L1.preheader ]
      %"#s2.0" = phi i64 [ %26, %idxend ], [ 1, %L1.preheader ]
      %19 = add i64 %"#s2.0", -1
      %20 = icmp ult i64 %19, %16
      br i1 %20, label %idxend, label %oob

    oob:                                              ; preds = %L1
      %21 = alloca i64, align 8
      store i64 %"#s2.0", i64* %21, align 8
      call void @jl_bounds_error_ints(%jl_value_t* %10, i64* nonnull %21, i64 1)
      unreachable

    idxend:                                           ; preds = %L1
      %22 = add i64 %"#s3.0", 1
      %23 = load i64*, i64** %17, align 8
      %24 = getelementptr i64, i64* %23, i64 %19
      %25 = load i64, i64* %24, align 8
      %26 = add i64 %"#s2.0", 1
      %27 = shl i64 %25, 1
      %28 = load i64*, i64** %18, align 8
      %29 = getelementptr i64, i64* %28, i64 %19
      store i64 %27, i64* %29, align 8
      %30 = icmp eq i64 %22, %13
      br i1 %30, label %L4.loopexit, label %L1

    L4.loopexit:                                      ; preds = %idxend
      br label %L4

    L4:                                               ; preds = %L4.loopexit, %top
      %31 = load i64, i64* %9, align 8
      store i64 %31, i64* bitcast (%jl_value_t*** @jl_pgcstack to i64*), align 8
      ret %jl_value_t* %14
    }

    julia> @code_native double([1,2,3])
    L128:L159:L177:	.text
    	pushq	%rbp
    	movq	%rsp, %rbp
    	pushq	%r15
    	pushq	%r14
    	pushq	%rbx
    	subq	$40, %rsp
    	movabsq	$jl_alloc_array_1d, %rax
    	movabsq	$140140655870352, %rdi  # imm = 0x7F750A030190
    	movq	$4, -56(%rbp)
    	movabsq	$jl_pgcstack, %r15
    	movq	(%r15), %rcx
    	movq	%rcx, -48(%rbp)
    	leaq	-56(%rbp), %rcx
    	movq	%rcx, (%r15)
    	movq	$0, -40(%rbp)
    	movq	$0, -32(%rbp)
    	movq	(%rsi), %r14
    	movq	8(%r14), %rbx
    	movq	%rdi, -32(%rbp)
    	movq	%rbx, %rsi
    	callq	*%rax
    	movq	%rax, -40(%rbp)
    	cmpq	$0, %rbx
    	je	L159
    	xorl	%ecx, %ecx
    	movq	8(%r14), %rdx
    	nopw	%cs:(%rax,%rax)
    	cmpq	%rdx, %rcx
    	jae	L177
    	movq	(%r14), %rsi
    	movq	(%rsi,%rcx,8), %rsi
    	shlq	$1, %rsi
    	movq	(%rax), %rdi
    	movq	%rsi, (%rdi,%rcx,8)
    	incq	%rcx
    	cmpq	%rcx, %rbx
    	jne	L128
    	movq	-48(%rbp), %rcx
    	movq	%rcx, (%r15)
    	leaq	-24(%rbp), %rsp
    	popq	%rbx
    	popq	%r14
    	popq	%r15
    	popq	%rbp
    	retq
    	movq	%rsp, %rsi
    	addq	$-16, %rsi
    	movq	%rsi, %rsp
    	addq	$1, %rcx
    	movq	%rcx, (%rsi)
    	movabsq	$jl_bounds_error_ints, %rax
    	movl	$1, %edx
    	movq	%r14, %rdi
    	callq	*%rax
It catches type errors early, thanks to the typed multiple dispatch.

    julia> xs = []
    0-element Array{Any,1}

    julia> push!(xs, 42)
    1-element Array{Any,1}:
     42

    julia> push!(xs, "foo")
    2-element Array{Any,1}:
     42     
       "foo"

    julia> ys = Int64[]
    0-element Array{Int64,1}

    julia> push!(ys, 42)
    1-element Array{Int64,1}:
     42

    julia> push!(ys, "foo")
    ERROR: MethodError: `convert` has no method matching convert(::Type{Int64}, ::ASCIIString)
    This may have arisen from a call to the constructor Int64(...),
    since type constructors fall back to convert methods.
    Closest candidates are:
      call{T}(::Type{T}, ::Any)
      convert(::Type{Int64}, ::Int8)
      convert(::Type{Int64}, ::UInt8)
      ...
     in push! at ./array.jl:432
Plus, I only have to think in one language, but I can write sloppy dynamic heap-allocating-everywhere code in the compiler and with just a bit of thinking emit zero-allocation statically-dispatched code in the output.


I would say there's something magical about the way that Julia is structured that makes it more than just a scientific language. I wrote a DSL for Julia for writing clean combinatorial verilog (haven't tackled sequential yet) in 3 days, using lispish macros. It took another 1 day to hook it up to 'verilator' which transpiles the verilog to C and lets you crosscheck it by loading it back up in Julia.

https://github.com/interplanetary-robot/Verilog.jl

This is very important if you're planning to build hardware to do specific math - because Julia is incredibly good at mathematical modeling, and you can very rapidly set up comprehensive tests for your hardware designs with confidence.

The closest alternative is chisel, which is written in scala. Although it's more professionally maintained and more fully-featured, it's hard to call the verilog chisel emits "human-readable", and it's harder to set up comprehensive tests - berkeley hardfloat, which is a very impressive project in chisel, had several critical bugs in its implementation (that I found, using julia).


That's really nice. Now we need VHDL.jl as well - maybe different HDL backends could be bolted on? Julia is much better suited for this than Scala due to Julia's macro system.


Although I don't know that much about VHDL, it probably won't be that hard to either fork it or make it an option.

Drop a line. My contact info is in my profile.


I'm not sure if "special language" is a particularly apt description of what Julia is. SQL is a "special" language.

At any rate - a lisp-like language with llvm backend, package manager, and a (small, but not trivial) group of users writing real software - what's not to like?

I (still) recommend the talk: "Julia - to lisp or not to lisp?", that gives a quick overview of some of the design choices:

https://youtu.be/dK3zRXhrFZY

For me, I like that it has a real story trying to balance "actual integer math" (not this silly machine constrained twos-compliment hack" and "you could conceivably write a ray tracer that wasn't unusably slow" :)


As a scientist, it's nice to have an option where I can easily espress my problem, and get a speedy solution at the end. Julia hits this sweet spot.

I translated some of my code from one of those general purpose languages to Julia 0.2, years ago. It was both more readable, and 20x faster. Never looked back.


In R, I can import a CSV, plot a histogram of each column, and fit a linear regression of one column against the others in about 5 minutes and 15 lines of code.

In Python, I can do the same thing if I install the Pandas and Statsmodels libraries first.

Try that in Ruby, Perl, C++, C, Java, Rust, Haskell, Common Lisp, or just about any language you can think of. Good luck.


The other great thing about R is the amount of information it outputs when you run a stats function.


It's easy to forget that R inhabits a gray area between a full-fledged programming language and a "statistics package" like SAS or SPSS or Stata or GRETL.


Languages like Julia, R and Matlab offer a whole lot in the way of built-in syntax, data structures, functions and constants for scientific computing that you would have to get from libraries in general purpose languages. But unless those languages have the kind of libraries that Python offers, such as Numpy and Pandas, you're not going to have that kind of support.

In addition, scientific languages will have libraries mostly in that domain. You can't beat R's library when it comes to statistics. They will also have good plotting libraries.

What you also get with Julia and Fortran is code designed to be optimized for numerical computing. Python attempts to offer this kind of performance via Numpy, which is Python wrapper on C or Fortran BLAS library. Or by JIT compiling with Numba, which is something Julia does automatically the first time you call a function.


One of the big wins is being REPL/workbook-focused, rather than REPL/workbook as an afterthought.

When I'm building software, I tend to like things to have very tight interfaces -- box everything up into components, understand how they talk to each other, etc. Define interfaces, implementations, types, etc -- In general, optimizing for long-term maintainability.

When I'm exploring data, my thought process is much more "scatter everything about on the desk" and "let me run these 5 lines of code again within the current context". "What does this thing look like", etc. Having "a table of data" as a first-class citizen in the language, with all the libraries assuming that as input and everything optimized to work around / display / visualize such is incredibly useful.


I've done mathematical programming in both C++ and MATLAB. As much as I hate MATLAB as a language, it's way faster and easier to prototype things. There's a huge library of vetted functions for scientific things I'm interested in, and having matrices built into the language is great. MATLAB has tons of warts though, so I'm looking forward to switching to Julia this summer.


One of the biggest warts is the casual elision of vectors and nx1 matrices... (and scalars and 1-arrays and 1x1-matrices). Julia wrestled with this in its early days, but I think the way it does things now is quite nice.


Let's not forget the complete lack of package management, where it guesses at which of the many overlapping functions you intended to call.


Because in R NA is built into a language.

So it understand the concept of missing value and works with logical operation. Also it have factor concept into the language too.

General Language may not have the tool to deal with missing value and having a library doesn't necessary means it's good as a built-in concept NA.

Python panda and such does not handle NA as beautifully as R. (http://pandas.pydata.org/pandas-docs/stable/missing_data.htm...)

Python Panda default to Null and before that it uses something else to represent missing data. Null value in general is the last thing you do when you don't know how to represent a certain type. If you have a type language with sum type and pattern matching then you don't even need null. And yes R have its own Null type so NA is separate from it.

Erlang have PID as a primitive.

I think any domain specific languages are smaller in rules and syntax, and it makes it very very easy to learn for experts and people of those domains.


>Why do you need a special language when any general language + some libraries will do?

Because you need special math support, easy access to suitable operators (and/or overloading), etc.


People don't "need" special languages. They just like them.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: