In a few days, I’m going to go try to sell (as in persuade, not monetarily) a short Julia course to some first year MSc AI students.

The majority of these students come from a comparatively stronger mathematical background. So far, they have been taught PyTorch and will use it to submit to the NeurIPS reproducibility challenge.

Problem

One of the key strengths of Julia is its type system and particularly how it interacts with multiple dispatch. It’s not hard to sell a CS student that unique combination, but math-oriented students (in my experience) haven’t been introduced to the ideas that

You don’t have to use Python, and you might actually enjoy programming more if you don’t.

A type system can be incredibly useful, not only for correctness and error checking, but also for speed and modelling (mathematical) systems in general.

(Bonus) Dependency and environment management doesn’t have to suck.

I think the best way to introduce Julia is by example, where each example introduces a problem and explores how Julia solves it. Does anyone have a very short example of using the type system in a way that might awaken something in an unsuspecting scientist?

I can’t go past Unitful and how you can propagate units with almost no friction, then giving an example of defining your own unit (maybe a whimsical one) and using it to immediately do a calculation.

Huh, my reaction to this was that python ought to have an answer to this, but… not really, apparently. (of course, ML people will say that nobody cares about forward and that reverse is where the real magic is, and that isn’t wrong, but still…)

using Measurements
x = 2.0 ± 0.5 # \pm<TAB>
y = 5.0 ± 1.0
z = 7.5 ± 1.5
typeof(x) # Measurement{Float64}
(x + y) * z # 52.0 ± 13.0
(x + y) / z # 0.93 ± 0.24
exp(x) # 7.4 ± 3.7

and composing it with Unitful:

using Unitful
x = (2.0 ± 0.5)u"m/s"
y = (5.0 ± 1.0)u"s"
z = (7.5 ± 1.5)u"kg"
typeof(x) # Quantity{Measurement{Float64}, 𝐋 𝐓 ^-1
x + y # ERROR: not dimensionally compatible!
x * y # 10.0 ± 3.2 m
x * z # 15.0 ± 4.8 kg m s^-1

Off topic but quick comment of appreciation to Rafael’s seemingly wiki-like ability to provide an exact link to obscure pieces of the Julia manual and old Discourse posts

I’ve never seen MonteCarloMeasurements.jl before! Looks great.

One of the things that may well cause a strong impression in math-oriented people is the possibility of optimizing parameters of a simulation by automatically differentiating an objective function which is dependent on the simulation itself.

One example of this, a little bit artificial to make it simple, is here: ⚡ Pluto.jl ⚡ (it is somewhat slow to open, because of some large figure). There, in the “Planetary Motion” and “We can differentiate everything!” sections, I show a simulation of the earth trajectory which misses the period. Then, we can differentiate the end-point of of the simulation (after a year) as a function of the initial position of the earth, and optimize the distance of the earth to the sun to fix that small drift in the final position.

The example is somewhat artificial, but it is likely that the idea of being able to differentiate complete simulations and optimize and tune simulation parameters like that can be very appealing for people working with simulations or dynamic systems in general.

Of course all that depends on the type system, in such a way that if the simulation is written with generic types appropriately, automatic differentiation, error propagation, etc, can work all the way through it.

Several good advanced topics are proposed above, but since these are first-year non-CS students I think you should spend most time on the basics, and maybe introduce more advanced examples towards the end. Explain the benefits of multiple dispatch compared to object-oriented single dispatch, and demonstrate that in many applications (especially math heavy ones) giving the first argument special status is awkward. Show how the type system means Julia can compile specialized methods for each function depending on how it’s called, and how this is fundamental for Julia’s amazing performance. And point out that types need not be explicitly declared to reap these benefits. Explain why Python needs Numpy and other C-based accelerations and mention the two-language problem. Maybe compare some clunky Numpy calculations to Julia’s more elegant formulations. Etc.

All these points have been covered very well in other Julia introductions you can find in this forum and elsewhere online, so be sure to look around before you reinvent the wheel.

Personally, I think one of the coolest examples of multiple dispatch is in linear algebra and this is probably also bound to impress math students.
You want the eigenvalues of a matrix in python?
Sure thing, it’s eigenvals(M) . Oh, but what about a hermitian matrix? Well here the eigenvalues will be real and you can use a better algorithm so you should use eigenvalsh instead. What about an upper diagonal matrix? What about a 2x2 matrix where I can write down the solution directly without using any scheme? Or a 2x2 hermitian matrix?
In Julia I simply write eigen and do not have to worry about anything. The only step is defining your matrix to be hermitian and a static matrix in the beginning (way more intuitive if you ask me) and the compiler does the rest for you.
It’s not just about the optimization being faster that way. Its also that I do not need to put insane amounts of thought into my code which abstractions and classes I should find and who should inherit what. I simply write down my functions and as time goes on I include more specialized versions thereof as I encounter more requirements that need special treatment.
This fact that I can just start my programming projects and don’t have to scrap half my code just because I need a higher level of abstraction was definitely the reason why I was hooked almost immediately. Even further, I find libraries that others have written much easier to understand that way.

Not very science-oriented, but I think this a nice way to showcase multiple dispatch (disclaimer: I’m the author of this blog post): Rock–paper–scissors game in less than 10 lines of code. Good luck doing that so short and extensible without multiple dispatch (adding a fourth shape post-fact — i.e. outside of a class, in class-based object-oriented programming — without multiple dispatch is non-trivial).

I said this isn’t very science-oriented, but if you think about it, this kind of 2-body interactions have actually many scientific applications (which is also the example brought in the Wikipedia article about multiple dispatch).

Sorry to say, but the example, as you describe it, is not a selling point for multiple dispatch, because I you can do all of that with single dispatch in any other language (that supports it), even without ‘abstractions’ or inheritance.

Hm i see what you mean but I do think the multiple dispatch approach still comes through here. After all, what makes this so powerful is that there are also generic methods for very generic matrices, covering a wide range of use case way beyond what you can realistically achieve with overloading. There are other powerful libraries written for linear algebra but none of them seem to be as flexible as Julia’s while at the same time being easy to use and fast. In Julia, I can simply call Linear Algebra functions on Float128 types from some other package and it simply works.