A colleague of mine one day went to my office to ask me if I knew about the language Julia. I said I knew the name, but I had never used it till now. He told me something like
You should give it a go, it has all the feature you dream about a language.
HA. What a weird claim to make. You are willing to tell me Julia supports infinite type families???
No but seriously. Julia is a modern language with modern features. And since it is very well possible him and me will work on it at some point, why not give it a go!
[Smooth narrator voice] Krab was not ready for what was to come. Will they survive Julia and its whims? Find out in this SPOOKY TALE OF HALLOW’S EVE!!!
The thing introduces
“Okay Julia”, I told myself. “Let us see what you have got.”
So I went to julialang.org and looked around. On the pain page, you can see the feature of the language that the developers deem the most prominent:
- Fast, like Sonic the Hedgehog; I do love me some fast language!
 - Dynamic, like in, dynamically typed; ah. Okay. Sure.
 - Reproducible, bold claim, but I will take it.
 - Composable, not a word; but okay. There is this thing about multiple dispatch put forward, we will have to investigate.
 - General, as in: provides a bunch of stuff to work with, including asynchronous I/O, macros, etc. Woof! Impressive!
 - Open source, which, let us be honest, what kind of sh*t language is not, ha ha.
 
So anyway, a lot of promises, but I am not quite convinced yet. I guess the devs did not think it was relevant to put forward other specificities of Julia, and in particular, Just-in-time compilation, despite it being quite a thing, in my humble opinion.
At this point, I am not too thrilled, let us be honest. But let us see some code alright.
[Smooth narrator voice] And with that, Krab clicked on the manual, as a bolt of lightning cracked the sky in a loud clack.
The interpreter
First things first, Julia comes with its own installation tool, juliaup, that
seems to be the go-to platform to install Julia versions and packages.
I am not entirely convinced about specific package managers like that, but why not. It is decently made and seems to work like a charm; good.
The julia command is both the compiler/runner and the interpreter. If you type
julia in the command line without any argument, it will fire the REPL.
The REPL is very inviting. I like terminals with colors. It is also modern and ergonomic, especially because you can edit multi-line blocks, go up in the history, and so on. A very nice piece of work!
Okay let us write a simple function.
[Smooth narrator voice] Something wrong was afoot. Krab’s little heart was pounding as they observed the result the REPL gave back. Were they a joke to Julia?
Now of course this piece of code cannot work. Look closely, my recursive call is
to the function fib instead of fibo. I cannot be mad at Julia that it did
not understand what I was saying because what I was saying was dumb!
Well, in fact, yes, I was mad, a little bit. Because. Usually…
…compilers do not crash when the code is wrong.
Seriously. Do I need to fire up the debugger to see why my code is wrong?
Okay. Breathe. It is alright. Every compiler/interpreter/whatever this is has its own personality, I find. Haskell’s GHC is a bit of a “HMM, ACTUALLY” kind of person, GCC’s C compiler is a bit of an uncaring parent and its C++ compiler is utterly mad.
So I guess Julia is… Uh… Struggling. Or very mean, I am not sure right now.
Anyway, if you want to now: yes, I noticed the mistake, yes, I fixed it, yes, it worked.
But what a first contact this is.
Julia’s type system
One good thing about Julia is its type system. Yes, the language is dynamically typed, which to me is not always good news, but in the case of Julia (as in the case of Java and for similar reason), this is an important feature of the language.
One thing that I find exciting is that Julia supports the notion of subtyping.
Indeed, Julia lets you define abstract types, and then subtype these types to create a whole type hierarchy!
1 2 3 4 5 6 7 8 9 10 11 12  | 
  abstract type Abs end struct Conc1 <: Abs x :: Int64 y :: Int64 end abstract type Abs2 <: Abs end struct Conc2 <: Abs2 z :: Int64 end  | 
Neat!
“Wait”, I hear you say. “Can you put stuff in an abstract type?”
Nope.
“And… Can you have a struct subtyping another struct?”
Haha, nope.
And I mean, why would you want that? Oh, yes, because it would make sense, uh. Well, whatever, you cannot, and you will have to live with that.
It is alright though – by that I mean that this is not the worst thing in the world. Subtyping is mostly used in conjunction with multiple dispatch; that is, the ability to define several functions with the same name, and have the right one called based on the dynamic type of the argument(s).
1 2 3 4  | 
  function f(x :: Abs) ... end function f(x :: Conc1) ... end function f(x :: Abs2) ... end function f(x :: Conc2) ... end  | 
In the example above, whenever calling f, the compiler will select the correct
variant based on the type of the argument. In Julia, we say that f is a
function, and the various variants of f are methods. My hunch is also that
this is the root of the segfault problem I had earlier: when parsing, Julia is
not sure about what function exists of not, and it cannot give a definitive
answer. That being said, the runtime (especially the REPL) could do the
verification and crash with something more meaningful that just a segfault (tell
the user what symbol was not defined, typically).
One “consequence” of the multiple dispatch system is that a function is a
singleton object of type Function, with basically several apply “generic”
methods defined on it, one for each combination of arguments.
I do kind of understand this standpoint but I find it a problem that you cannot specify the input and output type of a function when you are writing higher order function. For instance:
In this typical scenario, I cannot define the input and output type of the
function, and so I cannot declare the output type of mymap, and when calling
mymap I cannot make sure the function I pass as the second parameter takes a T
as input…
And of course, what happens when I try to execute mymap("abc", x -> x + 5)?
The road to define a fix point in Julia
[Smooth narrator voice] And so Krab decided all of that was kind of alright, and to continue their investigation. Their gentle colleague sworn that never, NEVER had he witnessed such segmentation fault, and that maybe, just maybe… It was all Krab’s fault (sike). So Krab went ahead with a crazy idea…
You know what is missing from Julia? A way to write recursive functions using fix points.
Okay so first, let us circumvent the input/output type problem by defining a
wrapper for Function:
1 2 3 4 5  | 
  struct TFun{From,To}
    fun :: Function
end
mkFun(:: Type{From}, :: Type{To}, f :: Function) where From where To = TFun{From,To}(f)
 | 
TFun is for Typed Function. It is basically a normal function
annotated with an input (From) and output (To) types. I also define the
mkFun constructor, that takes the input and output type as well as the
function itself and builds the TFun.
Of course, no verification is made by Julia to check that f is consistent with
From and To but the idea is to say that, if the user is reasonable, the
thing will work okay.
One thing I find hilarious in Julia is that you may overload function call:
With that piece of code, whenever I have myfun :: TFun{A,B} I can write
myfun(x) and will call the wrapped function. That is quite neat, in fact!
Now, let us override function composition:
1 2 3  | 
  function ∘(f :: TFun{B,C}, g :: TFun{A,B}) where A where B where C 
    mkFun(A, C, f.fun ∘ g.fun)
end
 | 
Yes, ∘ is the operator for function composition in Julia (because it is a language for mathematicians). You will note how beautiful it is that we can force the input and output type of the function to ensure type compatibility when doing composition!
And now, for my next trick, let us write the currying transformer!
1 2 3  | 
  function curry(f :: TFun{Tuple{A,B},C}) :: TFun{A,TFun{B,C}} where A where B where C
    mkFun(A, TFun{B,C}, a -> mkFun(B, C, b -> f.fun(a,b)))
end
 | 
Now, let us do the unthinkable: define a fix point function.
1 2 3  | 
  function fix(f :: TFun{TFun{A,B},TFun{A,B}}) :: TFun{A,B} where {A,B}
    mkFun(A,B, x -> f(fix(f))(x))
end
 | 
Now a few words on this. 1) It was surprisingly easy to write, 2) It was
surprisingly hard to write. In fact, initially, I wanted to write the real
fix, with type A → A → A, but I could not :(
I had to resort to write the lesser version, ((A → B) → (A → B)) → A → B.
But anyway, it works! We can write a factorial using that:
1 2 3 4 5 6 7 8 9  | 
  function fact_rec(rec :: TFun{Int64,Int64}, n :: Int64) :: Int64
    if n <= 0
        1
    else
        n * rec(n - 1)
    end
end
fact = fix(curry(mkFun(Tuple{TFun{Int64,Int64},Int64},Int64,fact_rec)))
 | 
And now…
Okay. Right. Julia does not give me a clue about what’s wrong with that. And of course the debugger is… Well I do not want to talk about it okay.
After a long moment of trying stuff out, I figured this worked:
1 2 3  | 
  fact_recF = mkFun(Tuple{TFun{Int64,Int64},Int64},Int64,fact_rec)
fact_recC = curry(fact_recF)
fact = fix(fact_recC)
 | 
And finally, I can write:
“Yay!”, I said before laying on the ground and stare at my ceiling for a solid hour.
Conclusion
[Smooth narrator voice] And so Krab achieved his descent into madness, writing a stupid operator nobody asked for in a language that clearly does not like them. But needless to say, they could not even begin to fathom what was to come…
So yeah, in fact, Julia is a neat language. I think it suffers from being a bit “young” (although it has been around for more than a decade), and maybe a user base that is mostly concerned by mathematics, and do not really push the language past its limit (maybe?).
Julia wants to be the successor of Python and Matlab, and, quite frankly, I hope it will be. I could ramble hours about how Python and Matlab are terrible, terrible languages, and it is true that, at least on paper, Julia is fixing most of the problems they exhibit.
I can only hope for a wider adoption of this language, which will inevitably lead to more improvements and more features.
In the end, I find it to be quite a reasonable modern language, even though there is no trait or object system in it (sigh).
Wait what is to come and why cannot I fathom it??



