> Yes, multiple dispatch is not some highfalutin ivory tower concept that only comes up in specialized code. For example, the model in question could define custom plotting recipes[1] so that you can just call plot() and have it produce something useful.
This is literally the whole conception behind generic functions in R (print, plot, summary etc).
I agree it's great, but Julia is building on a lot of prior art here.
But the performance relies on the aggressive specialization which depends on multiple dispatch. And the adaptation to numerical computing, the cleanness and beauty is all about multiple dispatch.
To stay with the bike analogy, multiple dispatch is definitely the wheels, not the motor.
> for one, I 100% agree with your views on variable naming elsewhere on this thread ;)
Waaah! (pulls hair.) And yet, so far apart on the function naming ;)
But seriously, though. Without the incredible polymorphism and genericity, there's really nothing at all left of Julia. Multiple dispatch isn't a feature bolted onto Julia. It is the core philosophy, and the central organizing principle.
> The key to make multiple dispatch work well is that you shouldn't have to think about what method gets called. For this to work out, you need to make sure that you only add a method to a function if it does the "same thing" (so don't use >> for printing for example).
Thanks for writing this. I think it is an important concept for getting started with Julia. When I tried Julia, I was initially confused and concerned about the subtyping hierarchy, which as far as I understand is undocumented. "Apart from a partial description in prose in Bezanson [2015], the only specification of subtyping is 2,800 lines of heavily optimized, undocumented C code." [0]. Assurance that users can safely ignore the subtyping hierarchy if we maintain semantic equivalence between methods, and that this actually works out in practice, makes it easier to commit to using the language.
I don't understand why people keep talking about multiple dispatch like Julia invented it. You can do that in many other languages, even languages designed for numeric computation. What's cool about Julia is that it has brought 90s compiler technology to scientific computing: a field which still thinks MATLAB is a really good way to develop and communicate scientific ideas.
> The pervasive multiple-dispatch in Julia provides such a beautiful way to architecture a complicated piece of code.
On the other hand it makes function discovery almost impossible [1]. Combined with the way how 'using' keyword exports a predefined subset of functions, this makes the language doomed from larger adoption outside of academia at least as long as there is no superb autocompletion and IDE support.
The promise of multiple dispatch is that it allows every library to be generic. One author writes a library that calculates $COMPLICATEDTHING; another author writes a library that implements a new type of number, such as ranges; provided the second library defines all the methods that the first uses, then, magically, they work together and you can calculate $COMPLICATEDTHING on ranges! That is the thing that Julia does that nothing else does.
> But the performance relies on the aggressive specialization which depends on multiple dispatch.
This is not a necessity, but an implementation choice. "Specialization" is an unnecessary step when everything is explicit from the beginning. I'm not talking on philosophical grounds, but thinking on concrete examples of jit systems which are really fast but have nothing to do with multiple dispatch (for example luajit).
> Without the incredible polymorphism and genericity, there's really nothing at all left of Julia.
If "matlab with fast loops" is "nothing" to you, sure. As a user of Julia, this is the killer feature for me. The rest I see as unnecessary complexity and mumbo-jumbo. But there's nothing wrong that each user has different favorite parts of the language!
Multiple dispatch, as in Julia, Mathematica, CommonLisp, etc, does get you much closer to this vision than single dispatch. To an extent, but then you're on your own again.
Maybe consider compilation as a collaboration of a computer and a wetware compiler. You have knowledge and do analysis which you are unable to share with the computer compiler. For silly tiny illustration, you might know some size variable has to be an integer power of 2, but most language's type systems don't let you tell the compiler that. Or think of writing a datatype in old C - a struct and a bunch of functions. You have a mental model of how they should all hang together, and then you mentally compile that down and hand emit C code and tests. Or you could hand emit assembly code and tests instead. But the C compiler is the more helpful collaborator. You can talk about more (with some sacrifices), and more easily. Now given say two structs and a bunch of associated functions, multiple dispatch allows weaving them together without pains which in single dispatch so discourage weaving. Which is better, but still, you've a couple of structs, a bunch of functions, and a still limited ability to express your intent so the computer compiler can help you with it.
So you can have multiple dispatch of + and - , and more easily add new numeric types, but any relationship between + and - is still all in your mind - the compiler has no idea that you think them connected.
Indeed, Julia's abstract type system with multiple dispatch is its killer feature. It enables generic programming in a beautiful and concise fashion. Here [1] Stefan Karpinski gives some explanations of why multiple dispatch is so effective.
I'll give you my two cents, recognizing that I very well might just be ignorant about Julia and multiple dispatch, and if so please continue to educate me.
Consider if we want to run many different types of models. Logistic regression, gradient boosting, NNs, etc. We want the ability to easily plug in any type of model into our existing code base. That's why model.fit(X,Y) is attractive. I just need to change "model = LogisticRegressionModel" to "model = GradientBoostingModel" and the rest of the code should still Just Work. This is a big part of SciKit's appeal.
But all these different models have very different training loops. So with "fit!(model,X,Y)" I need to make sure I am calling the compatible "fit" function that corresponds to my model type.
You might now say "Ah! Multiple dispatch handles this for you. The 'fit' function can detect the type of its 'model' argument and dispatch execution to the right training loop sub function." And I suppose that's theoretically correct. But in practice I think it's worse.
It should be the responsibility of the developer of "model" to select the "fit" algorithm appropriate for "model." (They don't have to implement it, but they do have to import the right one.) The developer of "fit" should not be responsible for handling every possible "model" type. You could have the developer of "model" override / extend the definition of "fit" but that opens up its own can of worms.
So is it possible to do the same thing with "fit!(model,X,Y)"? Yes of course it is. It's possible to do anything with any turing complete language. The point is, which system provides the best developer ergonomics via the right abstractions? I would argue, in many cases, including this one, it's useful to be able to bundle functions and state, even if that is in theory "less flexible" than pure functions, because sometimes programming is easier with less flexibility.
I'd say it's more readable. Here's why I like to read Julia code more than Python code (and Python code is normally already pretty readable):
* multiple dispatch in Julia allows to write shorter functions for each specific type and type parameters, while in Python you have to check them all in function's body. E.g. `numpy.dot(a,b)` needs to check type and number of dimensions for `a` and `b` in the same place, while in Julia `` is overloaded for each pair of arguments.
composite types in Julia normally include all fields with their types during declaration. In Python fields may be added wherever in class definition, types are not specified at all
* Julia has much broader set of overloadable operators (e.g. dot-operators like `.*`). Together with multiple dispatch it makes creating custom data types (e.g. custom array types) much easier.
> does it have more libraries
Definitely not, but it has very good relations with other languages. Calling a C function boils down to one line of code, calling Python or Java - couple of lines, never tried to call R or Matlab, but it seems to be fun too. In general, I've got much more pleasant experience than with any other pair of languages.
> can it be run in parallel better
Julia support (1) concurrency via tasks/coroutines, (2) shared-memory parallelism via threads (v0.5 only) and (3) isolated multiprocessing on local and remote machines. Most other scientific languages support either only (3) or (1) and (3) (Python 3+ version).
There's also a couple of unique features in Julia. My favorite is metaprogramming support. For example, currently I'm working on a library for symbolic differentiation from source code - something that would be very hard to achieve without direct access to AST. Also, macros allow to reduce boilerplate code a lot and make it easy and straightforward to create DSLs.
Julia is the first language to really show that multiple dispatch can be efficient in performance-critical code, but I'm not really sure why: JIT concepts were certainly familiar to implementors of Common Lisp and Dylan.
Multiple dispatch turns out to be truly awesome for mathematical code. In my opinion, the focus and careful selection of features for technical computing, while being a general purpose programming language is what differentiates julia from other dynamic languages.
Yes indeed. While Julia does generally eschew the "model.fit(X,Y); model.predict(new_data)" style, it's not because it's functional, it's because it's dispatch-oriented, which is arguably a superset of class-based OO, and even perhaps arguably closer to Alan Kay's claimed original vision for OO than modern class-based OO is.
> In CLOS one would not like that design. Though there also might be generic functions with many methods. My default CL has for example 84 print-object methods, some might have hundreds. Though generally it is not seen as desirable to group semantically very different operations under one name - especially given that CLOS provides different types of method combinations and a CLOS generic function might create a more complex interface.
Totally agreed here. However, addition, multiplication and so on have a very uniform and well defined set of semantics that apply to many types from all the various representations of real and complex numbers to the many many different types of matrices in Julia's LinearAlgebra library (lots of wrappers for things like Symmetric, or Tridiagonal matrices, lazy matrix factorization objects, adjoint matrices, etc.)
That's why there's so many methods on addition and multiplication in julia, and why I found it so surprising that CL doesn't do this on purpose. But I get that scientific computing isn't as big a part of the demographics in the CL community so I guess it makes sense.
> Some of these languages support abstractions which are strictly equivalent to static multi-method dispatch
Yes, and even with languages that have these abstractions, they aren't as pervasive as in Julia. Ex. There's a fundamental difference between typclasses and functions in haskell. In julia, there's no such distinction and code is more generic
I don't have a CS background, so maybe that's how I'm missing the point here, but I don't see what is so supposed to be so special about Julia's multiple dispatch. In fact, reading more about it and knowing Python, single dispatch/polymorphism feels more natural to me. This would allow a `plot` function in Python, originally written for the float data type, to also work for float-like (quacks like a duck) data types, like `Decimal`. I struggle to see what about Julia improves on this situation or makes it conceptually different.
Lastly, the article mentions that `plot` also works for uncertainty-carrying floats, because the `Measurements` package has implemented "a simple recipe" for this case. But doesn't this mean very tight coupling? The `Measurements` author very specifically (if I understand this right) implemented a `plot` functionality. For the end user, this allows things to just work, which is great. But under the hood, it seems this depends on package authors having to be aware of all possible use cases for their library code.
This is literally the whole conception behind generic functions in R (print, plot, summary etc).
I agree it's great, but Julia is building on a lot of prior art here.
reply