Why Multiple Dispatch Is My Favorite Way To Program | by Emmett Boudreau | Towards Data Science

The Julia language is an incredible technical feat that has been a work in progress since around 2008 and has really made ground in the past couple of years. The language is exciting, science-geared, and fun to use with its unique paradigm and programming style. To understand the Julia programming language, you need to understand multiple dispatch.
Multiple dispatch is a generic programming concept that is built on the grounds of parametric polymorphism. What is interesting about polymorphism, in general, is that it is typically a quality of declarative and functional programming, but the quality itself seems to be better described elsewhere in other programming paradigms. Multiple dispatch is the backbone of the Julia programming language, but that is most certainly not where the programming concept originated. The programming concept of multiple dispatch was actually originally introduced in 1973, 48 years ago! The first implementation was put into a little language called ML, which was fabricated by one Robert Milner and other associates at the University of Edinburgh.
The language is quite similar to a lot of high-level programming and scripting languages that we have today. The biggest difference between ML and a lot of the more declarative modern languages is that ML was also a very imperative language, which seems a bit odd when you consider that the language is also functional. A lot of languages from the mid-’70s and onward follow this tone actually, which I think is an interesting observation. We always credit C++ for really inventing the concept of generics, but there were so many multi-paradigm languages coming out in the 70’s that it is nearly impossible to keep track of them.
Multiple dispatch is a programming concept that allows a programmer to write a function multiple times to handle different types. A lot of programmers tend to stray far away from the functional programming paradigm for its global scope. This is understandable, having everything be global, especially with exports like in Julia, can be very problematic. This is primarily true because of naming. I actually wrote an article all about namespaces in the Julia language a long time ago, where I discussed why an implementation of namespaces similar to C++ might be able to mitigate this issue. I think it is an interesting read, and if you would like to check it out you may here:
towardsdatascience.com
This problem can be especially prevalent with functions. This is because Julia is not an object-oriented language, therefore there isn’t really a class scope that we can use to make functions private in order to work with specific types, we just write methods to take our types inside of the global scope. This means that a lot of times we might have methods that are under completely different names that do the exact same thing. Of course, that is not the case at all in Julia, and the reason that is not the case is because of multiple dispatch.
Multiple dispatch is a way that we can apply function calls as properties of types. In other words, we can redefine the same function over and over again, but just plan for different types to be passed through it in order to essentially recreate the method to handle a new type. Consider the following example.
x = [1, 2, 3, 4, 5]y = 5function add_5(x)    x + 5end
The function above adds five to the argument passed as x. For this example, we would only be able to pass our y through this function. If we tried to pass x through this function, we would quickly realize that there is no method matching +(Array, Int64). This is of course because there is not really a way that we can add an integer to our array. We also wouldn’t want to get sloppy and write another function with an alternate name, like this:
function add_5_array(x)     [x += 5 for x in array]end
That would be ridiculous. Luckily, multiple dispatch was created with the intention of solving this very problem. Using dispatch, we can cast a type to our arguments on both of these functions, and when that type is passed through the function only the function for that particular type is going to run.
function add_5(x::Array)     [x += 5 for x in array]endfunction add_5(x::Int64)    x + 5end
Now we can pass both our x and our y through there without any worries at all.
The Julian implementation of multiple is akin to typical multiple dispatch, but on steroids. Not only do we get the added benefit of being able to call multiple dispatch really fast and in one line almost as if we are writing lambda expressions in Python, we also have the choice to write functions in both ways interchangeably. Furthermore, Julia has even more tricks up its sleeve that make multiple dispatch even better.
The first thing that really adds to Julia’s dispatch beauty is the ability to create sub-types. In Julia, most types are part of something called a super-type hierarchy. A super-type hierarchy simply allows us to create inheritance across methods with types that are similar and can be handled the same way by these functions. For example, say we have a function that can take an irrational number, an integer, or a float. We could limit what part of the tree this goes to by simply dispatching the real type from our function.
add5(x::Real) = x + 5
We could also include the complex type in the use of this function by instead going up the tree and calling on all numbers:
add5(x::Number) = x + 5
This means that not only can we dispatch our types, but we can also have other types inherit those functions that we have dispatched to our abstract types. If you would like to learn more about this number hierarchy I just touched on, I actually have an entire article on it that you could read here:
towardsdatascience.com
Adding to the beauty of dispatch is Julia’s awesome inner constructors. Inner constructors allow you to create multiple inputs to achieve the same output. These are basically initialization functions that can take any number of arguments, and then construct the type they are applied to out of the arguments provided. What is really cool is that dispatch will take care of whether or not someone is calling the inner or the outer constructor, so you could still make the type with the normal arguments, but you can also create it with a new argument. Consider the following example:
mutable struct broken_array    dim1    dim2end
Here we have a new type called broken_array. In order to construct this type out of a two-element array, we will need to populate the dim1 and dim2 data values. We could of course do this like so:
array = [5, 2]broken = broken_array(array[1], array[2])
However, that is some inconvenient calling for an end user to do. We could solve this with an inner constructor, like so:
mutable struct broken_array    dim1::Int64    dim2::Int64    function broken_array(x::Array)        new(x[1], x[2])    endend
The old call we did before will still be dispatched to create the type directly by calling the outer constructor, but now we have the extended functionality of calling the inner constructor with our array like this.
broken = broken_array(array)
Pretty cool, right?
Another great thing about this is that it also makes all of the types and methods in the language incredibly extendable. We can take any type or any method from any package, directly import it, and extend it to do whatever we want. The inner constructor for types does not even need to be within the constructor, as well, so we could really take advantage and write functions to create types that already exist from other packages and use them anywhere. Furthermore, we could even extend other methods with our own types. One really cool result of that is that all of the packages in Julia often end up using the same set of base methods that you might be used to for completely different types.
Of all the programming languages I have worked and dabbled in, Julia has easily become my favorite to program in. This is a direct result of multiple dispatch. The multiple dispatch concept in the Julia language allows for not only some pretty radical and expressive programming, but also the extension of essentially any method or type that is used in the language. Needless to say, that is quite a powerful concept. Furthermore, it is a great way to program scientifically, because often method calls and function definitions end up looking just as they do in the papers. Thank you very much for reading my article, and hopefully, it might have made you inclined to check out the wonderful gift that is multiple dispatch!