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

As soon as you enter a function in julia, it behaves like a static language though. Consider this example:

    julia> foo(x) = x^2 - 2x + factorial(4x)
    foo (generic function with 1 method)

    julia> @code_typed foo(4)
    CodeInfo(
    1 ─ %1 = Base.mul_int(x, x)::Int64
    │   %2 = Base.mul_int(2, x)::Int64
    │   %3 = Base.sub_int(%1, %2)::Int64
    │   %4 = Base.mul_int(4, x)::Int64
    │   %5 = invoke Base.factorial_lookup(%4::Int64, Base._fact_table64::Array{Int64,1}, 20::Int64)::Int64
    │   %6 = Base.add_int(%3, %5)::Int64
    └──      return %6
    ) => Int64
This is me querying Julia for it's typed intermediate representation of the function `foo` I defined if the input is an integer. As you may be able to see, this is a statically typed program. So while Julia is a dynamic language, the interior of function bodies can be static if type inference succeeds. However, if type inference fails, Julia is also perfectly happy to just let chunks be dynamic.

If you're having trouble understanding that code_typed output and are familiar with C, maybe the output LLVM instructions are helpful:

    julia> @code_llvm foo(4)
    ;  @ REPL[8]:1 within `foo'
    define i64 @julia_foo_17575(i64) {
    top:
    ; ┌ @ int.jl:52 within `-'
       %1 = add i64 %0, -2
       %2 = mul i64 %1, %0
    ; └
    ; ┌ @ int.jl:54 within `*'
       %3 = shl i64 %0, 2
    ; └
    ; ┌ @ combinatorics.jl:27 within `factorial'
       %4 = call i64 @julia_factorial_lookup_17503(i64 %3, %jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 140302197922128 to %jl_value_t*) to %jl_value_t addrspace(10)*), i64 20)
    ; └
    ; ┌ @ int.jl:53 within `+'
       %5 = add i64 %4, %2
    ; └
      ret i64 %5
    }

Also, I encourage you to note that nowhere did I manually put a type annotation in my definition of foo. foo is a generic function and will compile new methods whenever it encounters a new type.


You seem to confuse type checking with shape-driven optimization. Of course the latter can borrow a lot of terminology from the former and in a typed language you would be stupid not to use your types, but they are different topics. Any decent JavaScript or lisp compiler or interpreter will do the same.

Also what Julia does is not exactly type inference. It is rather a form of specialization. Type inference just gives you the type for an expression using just the syntactical representation. Nothing more.

As an example, if I was to invoke your function f with a floating point number, would Julia not happily specialize it for that too (assuming factorial was defined for floating points or taken out of the equation)? The thing here is, that a type checker needs to know about all the specializations a-priori.


Not sure what you mean by that example. If "f" accepts a float and you give a float it will work. If "f" accepts a number (or t subclass number) it will work since float subclass number. If "f" only accepts int then it will fail since you can't convert a float to int (as in there are no defined promotion/conversion rule from float to int). The type check works just like any language.

Specialization will only occur if there are more than one "f" that handles your type (for example both "f" with a number and with float), and then the compiler will choose the most specialized (float). If you write your Julia program like you would with a static language (for example using over-restrictive types like just float and int) it will return the same error as the static language as soon as it can infer the types (when it leaves the global namespace and enters any function). Using over-restrictive types (and declaring return types for methods) is considered bad practice in Julia, but that's a cultural thing, not a limitation of the type checking.


> The type check works just like any language.

It is not a type check if it happens at runtime.


It happens at compile time in Julia (required for the multiple dispatch to work), the compile time is just interlocked between runtime steps. Calling something with incorrect arguments will fail even if the function is never called during runtime (unless the compiler figures that out using only compile time information and decides not to compile it).

But I understand your point, it's not really like a static language checking as it can't do without running the program, so it's better than fully interpreted (a unit test would catch errors even in paths it didn't take) but worse than static checking (as it can't compile 100% of the valid program at once).


We do have all the information needed to do static checking at compile time in Julia instead of runtime, we just choose not to.


> As an example, if I was to invoke your function f with a floating point number, would Julia not happily specialize it for that too (assuming factorial was defined for floating points or taken out of the equation)?

Yes, but I could then define

    bar(x::Int64) = x^2 - 1
and this would create a method which only operates on 64 bit integers.

Now I want to be clear that I'm not saying Julia is a static language, I'm just saying that once specialization occurs, things are much more like a static language than one might expect, and indeed it's quite possible add mechanisms for instance to make a function error if it can't statically infer it's return type at compile time.

Here's a hacky proof of concept implementation, but one could get much more sophisticated: https://stackoverflow.com/questions/58071564/how-can-i-write...




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

Search: