A while ago I decided to learn a new programming language. I don’t exactly remember why but I felt like I wanted to. There were two candidates I was interested in, Rust and Julia. A colleague of mine really loves Julia and I’ve read a bit about it so I was interested enough to give it a try. Also, it is heavily geared towards scientists and scientific computing including data science so it seemed like a natural thing for me to try.
At that point I still decided to go with Rust and I wrote a separate post about that. After a while though there was a bit of a lull in my Rust activity (as it was a bit overwhelming to me at times) and I decided to look into Julia anyway as it had seemed much simpler to get into and understand.
I decided to just go and read its documentation. I also had a look at this bit that highlights the differences from Python so the people coming from there would know what they were getting into.
Some things I liked. For example the fact that using operators or Greek letters as variables is straightforward because you
can simply type, e.g.
\pi and hit
Tab for it to appear which is the same syntax that LaTeX uses which many scientists (myself
included) are familiar with.
Also, Julia has types. I’ve come to appreciate a static type system because it helps me stay sane when reading other people’s
functions. Of course it also helps me with coding something that makes sense.
Julia has a dynamic type system. Hmm. Still it seems saner than Python in that respect. I spent some time reading about this. Basically, it seems, you can not explicitly use the type system most of the time unless you accidentally trip up the compiler so you can then help it along. Fine with me.
Some things, I was unsure of, like the fact that indexing is one-based. I’m not one of those people who have a strong opinion on that but I felt like this might be an inconvenience because most other languages have zero-based indexing and by now I’m used to that. Except MATLAB. Screw MATLAB. Julia also makes a point of not having any object orientation. There are structs but you cannot associate methods with them. Instead, you write functions which may take structs as arguments which achieves the same thing. It is claimed that this provides the user with more flexibility of how to write code. Also, functions can be overloaded in the sense that you can write the same function with varying arguments and implementations and have the compiler select the right one depending on the types of the arguments it is given. This multiple dispatch is one of the most prominent features of Julia.
Fine, I think, but I’m kinda wondering if this won’t lead to a lot of namespace pollution since one of the reasons why OOP exists in the first place (the most important reason, according to some) is encapsulation. Maybe Julia deals with this somehow differently but I wasn’t able to find anything about it.
Julia also has a REPL, same as Python, which is fine, I guess, although I don’t care too much either way. I’m not much of a REPL person and have never used it much for Python either, just for quickly testing syntax or so.
Looking at libraries, I found all the stuff one might expect for a language such as Julia. There are web frameworks, sure, but also kernels for Jupyter, libs for plotting data frames, numerical computations, all good.
Ok, I thought, why not just try and do something with the language then. I happened to have some data on hand that needed visualizing and I judged this might be as good a place to start than any. It sort of went downhill from there.
See, normally I’d write a short Python script that reads in the data I have, create a plot with
matplotlib and then look at that.
I even used Jupyter for that recently, although I’m not particularly fond if it, but for looking at plots it’s fine.
I did the same thing in Julia using
DataFrames.jl. It’s very easy to use and similar to
in Python. So I wrote that script, saved it and ran it. And waited. After some 30 seconds or so the plot finally appeared.
That was a tad long for my taste, especially since the same task does take significantly less time with Python which is always
trashed as slow. I investigated and discovered that this is a well-known problem, known as the “time to first plot” problem, or TTFP.
The reason is that, in order to produce the output, Julia has to load the libraries (which takes a while because
Plots.jl is huge) and
has to compile the code before running it. Every single time.
Julia uses just-in-time compilation (JIT) which means that you can write a program but have to compile it every single time before
you can run it and no binary executable will be created (this is possible but seems like an afterthought and something of a hack).
This annoyed me a lot. What is the point of having a compiled language that boasts with its speed when I can never reach the speed in those benchmarks because I have to recompile every time?
How should I deal with this?
There are a couple of ways around this, of which I tried two. The first was
DaemonMode.jl which starts a Julia daemon in the
background and sends the code that should be run to it. This is supposed to mitigate the startup and compilation costs. Maybe
I did it wrong but I followed the instructions and I didn’t really notice a difference. It seemed to me that the project was
not quite mature yet. It seemed like a decent idea to me but also a bit of a hacky workaround.
The second thing I tried, which also appears to be more like the “Julia” way, was to run my code in a REPL. There is a library
Revise.jl which you can use with this workflow and this will work something like this: you write your functions,
you execute them in the REPL, the code is compiled and run. When you change the function and run it again it will basically
not take any extra time (except the first time, actually). So you can, e.g., write a function to read in data and plot it and thus
get rid of the overhead of loading and compiling the libraries you use.
This works but it felt exceedingly tedious to me. One way or another, you always have to have Julia running somewhere in order to circumvent the need to recompile the whole code every time you want to run it, whether you made any changes or not. This was the deal-breaker for me. I just don’t like REPL-focused workflows. Call me old-fashioned but I prefer compiling, getting an executable and running that. Easier to distribute and share, and most of the time this is faster even. I just don’t get the point of JIT compiling, it seems like a pretty big drawback to me.
Maybe someday I’ll try again and find that things have changed and the TTFP problem has been solved for good. Maybe I’ve also
been doing it wrong or I misunderstood something fundamental. I suppose some would say that my problems are actually non-issues
because there are things like
PackageCompiler.jl that create binaries from code. My point is, however, that such things were
only created after the fact and make me jump through hoops to achieve something I would have liked from the very beginning.
Programming languages are all about trade-offs, yes? Well, I’m not quite happy with the one they made here and trying to fix
it afterwards doesn’t change that.
Also, I will say that first (and second) impressions count for something and whereas my first impression of the language after reading a bit of the docs was good, my first coding experience was not an enjoyable one.
Sorry Julia! It didn’t work out between us. Maybe I’ll come back one day but for now I’m happy elsewhere.