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

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.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

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

Search: