The issue/different from “overloading” is that in C++ there’s also the concept of compile time type in additional to the runtime/true type of the object. It is indeed much closer in concept with static vs dynamic dispatch (at least some meaning of it).
FWIW, I’ve seen dynamic vs static dispatch to mean either a compiler optimization vs the use of compile time/runtime type so the confusion is understandable. In the context of julia though, compile time type simply do not exist and the meaning of static vs dynamic dispatch is reasonably clear.
Not as much in C++, where you have the use of compile time (overload, multiple argument) vs runtime (virtual function, single argument) in the language spec and also compiler optimization (devirtualization) to call virtual function statically.
And that’s why I said this absense of compile time type is the only difference and the main confusion. C++ style overload simply can’t exist without it and if one tries to look for such thing one can easily confuse it with the real type of the object (due to compiler optimization/specialization) and then conclude that multiple dispatch is just overloading.
I agree. I think the main confusion here is that the OP should be referring to “dynamic dispatch” instead of multiple dispatch. As it is, the discussion here doesn’t make much sense from the point of view of what the manual of Julia means by multiple dispatch. Discussions based on
“When I use a word,” Humpty Dumpty said in rather a scornful tone, “it means just what I choose it to mean – neither more nor less.”
I think I already conceded that you can call something multiple dispatch if it can (but not necessarily) occur at runtime. Your C++ example uses a virtual method to force overriding and runtime binding in inherited classes. I accept that C++ OOP can be called dynamic dispatch because it can occur at run time as well as compile time.
I have accepted that Julia does do multiple dispatch, but my point is that most of the time that dispatch is not dynamic but equivalent to overloading in a static language. I’m paraphrasing but your counter is that Julia is at runtime all the time, and the compiler is just an optimization over type labels so when that dispatch occurs doesn’t really matter - because you are at runtime all the time.
I think the distinction matters because we use dynamic dispatch when we have uncertainty about which methods need to be called, in C++ that’s virtual which is distinct from regular method overriding. Either way that uncertainty has to be paid for somehow, mostly with a performance penalty because the call occurs at runtime - just when the function is to be executed. A lot of the examples of multiple dispatch in Julia have no uncertainty about which method needs to be called, in fact practically speaking most of the time there’s no uncertainty and no need for dynamic binding. Which is why I re-wrote the example in D using only overloading which would be the equivalent to what the Julia code actually does - there is no uncertainty. If you add uncertainty, you force true dynamic dispatch to occur - which can not be emulated in a static language without runtime dispatch.
The fact that Julia goes to great pains to invalidate that “static” code as you define new methods makes this much less clear to me. Even in the cases where the dynamic dispatch happens to be compiled away, the language has to be prepared to throw that away to preserve its dynamic behaviors.
I get what you’re saying, but I’m not entirely sure what the take away is. We seem to have definitively answered the initial question that the thread started with: what Julia does is dynamic multiple dispatch and not function overloading. With a note saying something like “but function overloading is kind of like multiple dispatch except when it does the wrong thing.” Or maybe the note is more like: “Julia’s implementation of multiple dispatch is remarkably good at avoiding dynamic dispatch costs, but if your situation is sufficiently dynamic, sometimes you still have to pay for it.”
Yes something like that but I think examples showing multiple dispatch should lean towards things that are not easy to implement using overloading in a static language.
In C++, by default, you have static dispatch, i.e. you need to annotate a function as virtual, in order to have dynamic dispatch. On the other hand, in Julia every method is “virtual” (although it’s more general than that since methods are dispatched on every argument type, not only this , using the most-specific-declaration rule).
Do you have any suggestions for how that can be phrased so that it’s more clear to you and others who share your intuitions and mindset?
This is not true in C++. The C++ compilers these days are fully capable of devirtualizing function calls by specializing (cloning) functions, inlining and inter procedual optimizations. It does not always do that of course, since the compiler may not always have the full picture.
It’s also not true for julia. When you write code like f(a, b) = g(a, b) there’s absolutely no way to figure out what method of g it is calling, just like C++. You always need context.
The only difference here that you need to get used to is that in julia, doing f(a, b) = g(a, b); f(x, y) is never going to be differernt from g(x, y), unlike C++, since the signature of f has absolutely no effect on the dispatch, and so there’s no point doing that in an example. Also, for the same reason, since the signature of the function generally have everything to do with dispatch in C++, people will just write the example like f(a, b) = g(a, b) without the f(x, y) which will look like nothing is known about the dispatch even though in both cases there’s no difference.
To make my point more clear. A C++ example,
// ----
struct Base {
virtual int g() { return 1; }
};
struct Derived: Base {
virtual int g() { return 2; }
};
int f(Base &b)
{
return b.g();
}
// ----
int main()
{
Derived d;
return f(d);
}
The part in between // ---- is what you are going to see in a typical C++ example and since context is removed it’ll appear as if the dispatch is unknown, even though the value is always generated with a known type. And to proof the point, if you compile that code with either gcc or clang at O2 or higher, there’s no virtual function call at runtime. (Sure this is a simple example, but I’ll bet you that most C++ examples you’ll find are easy for compilers these days to optimize given the same kind of context you see in julia examples)
Now compare that to a julia example,
abstract type BaseT end
struct Derived <: BaseT end
f(::BaseT) = 1
f(::Derived) = 2
g(d::BaseT) = f(d)
g(Derived())
This will probably be shown as a whole in an example simply because there’s nothing in g(d::BaseT) = f(d) that tells you what this code is doing, the ::BaseT there never have any effect on the f(d), unlike in C++. Because of that, the context is generally included (since that’s the only way you get any idea about what object the code is running on) and it’ll look like the dispatch is known when the code is written, even though it’s every bit as uncertain as the c++ case since you can call g with a yet unknown derived type from BaseT.
(And another reason you’ll usually see context in julia example is that it makes the code runnable from the REPL, a fully runnable program in a C++ example is a much taller order although cppreference.com does a very good job and because of that, the virtual function example at virtual function specifier - cppreference.com can be compiled by a mordern compiler to have no ruutime dispatch)
Basically in both case, without the caller of g, it’s equally uncertain and with it it’s equally certain. The way C++ code is deployed typically does make one case more relavent then another but that has no effect on the spec of the language. JIT for C++ also exist which should be able to do specialization just like julia and writing a dummer julia compiler to make things looks more uncertain is trivial. That’s why the implementation difference is not very important…
I think that examples of multiple dispatch are often geared towards users of Matlab/Python/R etc. to show how it differs from class-based single-dispatch OOP (or even no dispatch at all). Whatever point you are making, it will be of interest to a limited audience of advanced programmers and computer scientists. Focusing on that in tutorials for people new to the concepts are likely to leave the audience baffled.
C++ objects are single dispatch, meaning that methods are bound to a single object, and methods can be static or dynamically (virtual) resolved. C++ has a notion of function overloading were multiple functions having the same name with different type signatures are resolved at compile time. In Julia every function or method is multiple dispatch meaning that whether or not the arguments have explicit type hierarchies, they are runtime though the specific way this occurs in the compiler varies depending on type specificity and resolution.
That’s what the C++ standard says (Standard C++) any deviation from that is compiler dependent optimization. My point still stands, that dynamic dispatch is for uncertainty about types.
And that’s exactly the case I talked about above where the “dynamic” does not have the same meaning as what is usually used in julia. Using that meaning, all dispatch in julia is dynamic.
The original question was whether Julia actually does multiple dispatch. I’m now satisfied with a statement that it does. For me it is still summed up by this example that’s what convinces me - without this kind of behaviour you couldn’t have dynamic dispatch. We could continue to argue semantics but I don’t think that would be very useful - in fact I probably wouldn’t continue to argue. Thanks to everyone for all the input. There’s plenty of food for thought here.
Just being curious: did you entertain the idea that the original announcement and the manual were lying, it’s just that they got away with it for 8 years before you caught onto them?
As I said before, my original framing was intended to be a little playful. I take full responsibility for any initial ire incurred as a result. To be clear, in no way shape or form am I actually accusing anyone in the Julia community of lying! But I think that Julia does blur the line between Dynamic and Static languages - there’s a lot of benefit in that which is one of the reasons the language has been propelled to popularity very quickly. It’s a dynamic language with a lot of the machinery of a static language. With all this in mind, it’s worth pushing the community from time to time for clarity. Also, along with all the usual examples on object oriented programming, if you really want people to understand multiple dispatch, it’s important to have examples that distinguish it from simple emulation using regular overloading in a static language.
Which part of the manual do you think is unclear? AFAICT Julia’s usage of multiple dispatch matches the commonly accepted definition.
My impression from this discussion is that it is you who happened to be confused about multiple dispatch, managed to start a topic about it using a somewhat unfortunate tone, then (hopefully) learned what the words mean. This is OK, but framing this as a service to the community may be unwarranted.
I am not sure about this. Maybe if someone is coming from a C++ background, a comparison helps, but if someone is just learning Julia, comparing to the details of another static language may be just a waste of time, compared to spending the same amount of time on just understanding Julia as is.