I used to love this Julia feature, up until the point where I needed to compile my code into a self-contained small binary that I can just execute and expect it to be working, which I can easily do in Nim while still maintaining very expressive code and very fast compile times.
No offense. I believe what you say is generally true, Nim is much more dynamic than Haskell (without extension) and can still get smoothly compiled . Though Iâam not a professional Nim programmer, but I still can see :
# Materialize the algorithm for float32
# ----------------------------------------------
generate foobar:
proc foobar(a: Tensor[float32], b, c: Tensor[float32]): Tensor[float32]
The signature of foobar is proc foobar(a, b, c: Fn): Fn
, so this is exactly the same situation in Julia, there is no way for the compiler to infer this type (because a,b can potentially be any legal type),you have to supply concrete type to force creating the instance of this function. In Julia, we can use already use precompile
to do so.
Having wrote many words under this post, what I just want to point out is that some dynamic behaviours can never get statically analyzed unless we get further information(so keeping a runtime environment like LLVM and JVM is necessary to get the information).
REPL and prototyping are two reasons why Julia should be dynamic, but thatâs not the whole story. I think (this is my own opinion), cocreators of Julia intended this type system to be such dynamic because itâs a suitable abstraction for scientific problems and they donât use it for safety checking(at least ,itâs not so important and it wonât alert if compiler encounters some ill-typed program). This the fundamental difference of Julia and other languages. Static type language lift information to types, so compiler can use the information to do many static analysis. In order to do such analysis as much as possible, types need to get âcomplexâ so they can hold more info. So I guess why there is no concept or typeclass or type family in Julia. This things are acually guarantee of type system, sematically they canât make type system more expressive at all, while in Julia, (abstract) types are designed to cooperate with mutiple dispatch, so expressiveness is important. Maybe outside this field, such as system programming or game programming, we donât need such a generic abstraction and itâs fine to restrict the type system to make it static (for example, devirtualize all multiple dispatchs), and with this restriction our program looks still expressive and compiler will be happier to do type inference.
But anyway making type system static will still somehow weaken expressiveness of type system. So although both Julia and Nim have something like generaic types, sum types or any,etc, they are not 1:1 map. It seems that dispatch or overloading is only a terminology difference. But supporting dynamic dispatch needs runtime support while static overloading donât add any essential expressive power to type system (itâs actually a grammer sugar,but I admit itâs convenient).
Open type system is game changer. If we want to extend types, then we canât avoid this. Many OO languages are actually open types because it allows user to extend the type as long as they can implement all the virtual methods of this class, but when you try to generate a binary file, this finite type world is actually closed since compiler can run through the whole program to gather all the information it needs, so they appear closed(However, class loader in Java is essentially open type,so there is no way to analyze them ahead of time) . I think Julia can do this too, but removing LLVM and Base dependency is little hard (can Java run without JVMďźI think some basic functions such IO, still need runtime support, so itâs not so easy), hoping that one day developer can solve this problem.
Many Julia developers benefit from this system and many features are widely used in libraries.
After a long time I come to realize that being dynamic is not just becuase we want REPL or we are just want convenience, but also that expressive type system (generic and union), mutiple dispatch and open types get bound togerther tightly, which forces the type system to be dynamic. Just as some Julia users in this post like ChrisRackauckas have said, many static languages (some exceptions including the dependent type language) bypass dynamic issues as if they donât exist. So letting the type closed makes multiple dispatch not so useful because we can always pattern match(or use âtypeclassâ in Nim to do some kinds of specialization at compilation time), then language will become more predictable. And without multiple dispatch(remember that Juliaâs multiple dispatch can return different types), open type will become something like typeclass in Haskellďźwe need to ensure they can be applied to some function) or they can become something extremely dynamic like those in Python(because you have to do if -elseif to specialize type by hand). So different parts reflect and corresond to each other, they are not placed together randomly. This also causes compilation hard, but to get the full power of the whole type system, we have to give up this to do so (And I think Julia is initially not designed to be compiled) . Just like in order to gain the full power of macro in Lisp, we have to write a lots of parentheses everywhere.