4 ms·
Numerical programming takes more expressive power than one might imagine. In most languages, numerical primitives like integers and floating-point numbers, and
by StefanKarpinski 10y ago
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-werent-numpy-scipy-numba-good-enough/2236/10 https://discourse.julialang.org/t/julia-motivation-why-weren....
- zengid 10y agoI 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
- jamii 10y agoJulia 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.