Claim (false): Julia isn't multiple dispatch but overloading

I think this is the only kind of instance where from the compiler’s point of view you are doing true runtime dispatch - because the function/method resolution occurs at runtime and the compile can not fully optimise randomAnimals. In a static single dispatch OOP language you’d have to do a.meet(b) (or the other way around) overriding the parent declaration of meet which in this case would have to be dispatched at runtime. A true static multiple dispatch language would allow you to do meet(a, b).

Either way when you run:

Or:

you are doing overloading from the compiler’s point of view. Because when you run that function, all the types are fully resolved.

I meant that a language can be multiple dispatch ( as defined in the Julia manual: Using all of a function’s arguments to choose which method should be invoked, rather than just the first, is known as multiple dispatch.), or it can be single-dispatch (as in Python), and at the same time it can be either static or dynamic. These two concepts seem to be orthogonal. Am I wrong?

The reason I bring this up is that these two concepts are conflated in this discussion, and it seems not to be helpful.

2 Likes

I’m surprised no one seems to have mentioned this, but @StefanKarpinski gave a nice talk at JuliaCon, which is available on YouTube, where he explicitly contrasted Julia’s multiple dispatch with C++ static overloading and showed an example of how the two differ: JuliaCon 2019 | The Unreasonable Effectiveness of Multiple Dispatch | Stefan Karpinski - YouTube

23 Likes

You are right, i clearly lack precision, because I tried to stay in the OPs wording (function for multiple dispatch and methods for overloading as in C++ or C#).

1 Like

If I was writing the original meet (Julia Basics: Multiple Dispatch) in D for instance, I’d just write:

import std.stdio: writeln;

//rdmd animals.d

struct Lizard
{
  string name;
}
struct Rabbit
{
  string name;
}

enum bool isAnimal(T) = is(T == Rabbit) || is(T == Lizard);

auto race(Lizard l, Rabbit r)
{
  return l.name ~ " wins in wall climbing.";
}
auto race(Rabbit r, Lizard l)
{
  return r.name ~ " wins in a normal race.";
}

auto race(T)(T r, T l)
if(isAnimal!T)
{
  return a.name ~ " and " ~ b.name ~ " run forever.";
}

auto meet(T, U)(T a, U b)
if(isAnimal!T || isAnimal!U)
{
  writeln(a.name, " meets ", b.name, " and they race!");
  writeln("Outcome: ", race(a, b));
}

void main()
{
  meet(Lizard("Larry"), Rabbit("Rose"));
}

That’s overloading, everything is fully resolvable at compile time. But I couldn’t write my version of randomAnimal using overloading because it’s runtime resolved only.

1 Like

You appear to just be using a rather different definition of the distinction between operator overloading and multiple dispatch than the definition used by most community members here. That’s okay I guess (though I must say, I dont think your definition is very useful), but the sort of semantic argument you’re engaging in is doomed to run in circles like this unless either you, or everyone who disagrees with you, realize that you’re just not talking about the same thing and make an effort to do a lot of translating.

What you really need to do if you want to approach a resolution to this is convince people that they should adopt your definitions.

I could show up at Lockheed Martin and proclaim “this vehicle is not an aircraft, but a car. Most of the time, its on the ground, thus it is a car, though it occasionally flies.” but I think we can all see why that wouldnt really lead to a productive conversation.

15 Likes

No. Multiple dispatch is dynamic dispatch and that happens at runtime. It means something very specific. If your function calls are fully resolved when you call them that isn’t multiple dispatch but overloading. People get confused when examples of overloading is passed off as multiple dispatch. I now acknowledge that Julia can do multiple dispatch, but I’m saying that the examples people use are not multiple dispatch but overloading because of what the compiler is doing; and that in fact most of the time when people think multiple dispatch is happening, it’s actually overloading.

Okay, that is the definition you are working under. That is not what the people here mean by multiple dispatch. Sometimes words mean different things to different groups of people. I’m sure that the definition you’re working under is more useful in the context of a static language. I don’t think it’s a very compelling definition here because Julia’s semantics truly don’t care if it’s running in a compiled or interpreted mode or if the compiler heuristics gave up on an optimization pass.

Repeating yourself is unlikely to make people here adopt your definitions.

7 Likes

I’m not a computer scientist by training, but I think it would be fair to say that to qualify as supporting multiple dispatch, the language must be capable of doing dispatch at runtime. That doesn’t necessarily mean it can only happen at runtime.

For example, the Wikipedia page says

Multiple dispatch or multimethods is a feature of some programming languages in which a function or method can be dynamically dispatched based on the run time (dynamic) type or, in the more general case, some other attribute of more than one of its arguments.[1]

But if you chase that reference, here’s what it says:

In multiple dispatch languages, more than one polymorphic arguments participate in method lookup.

There’s nothing there saying it must be at runtime.

13 Likes

The important thing is runtime type, not runtime dispatch. Whether the dispatch happens at runtime or compile time is entirely an optimization. Otherwise, a compiler looses the capability to do “multiple dispatch” when it becomes smarter, which is not a very useful definition, to say the least.

26 Likes

One way that I find interesting to phrase it is that julia always performs an operation who’s result is the same as the result of multiple dispatch. The specific implementation is not guaranteed, can change at any time, it use a combination of methods, but at the end of the day what the user sees is a result who’s output is indistinguishable from always performing dynamic multiple dispatch.

1 Like

Fair enough … in my defence almost every example of multiple dispatch in Julia are easily written using overloading in static languages. It would be helpful to make the distinction by using examples where method dispatch happens dynamically.

9 Likes

Use an Any[] array. Boom, run-time dynamic dispatch.

We do tend to push folks away from dynamic behaviors — and this can sometimes come in the form of scaremongering about the “type-instability boogieman” — but there’s nothing wrong with run-time dynamic dispatch. It’s a great and powerful feature. You’ll just see performance akin to a more traditional dynamic language.

12 Likes

I think you are still confusing multiple dispatch with overloading. As far as I know very few languages have multiple dispatch. Again, I point to the Julia manual for the definition of multiple dispatch as understood in Julia: Claim (false): Julia isn't multiple dispatch but overloading - #22 by PetrKryslUCSD

1 Like

And that’s because the concept of compile time type just doesn’t exist in julia, there’s only runtime types. It is then basically impossible to come up with a semantically equivalent example to, say, c++ function overload which is based on compiled time type. There’s just no way to declare a more abstract type for an object in julia.

You can of course take advantage of compiler limitation to force a runtime dispatch, but that doesn’t mean much either. It’s just an implementation detail and the way you use to fool the compiler today may not even work tomorrow. Even with a load from an Any array, if the compiler can see how the array was constructed.

5 Likes

I don’t think so. The section on methods in the Julia manual you are referring to does not distinguish between runtime and compile time. My original point was that there is no real difference between what people in the Julia community call multiple dispatch and what is technically overloading when types are fully resolved. They are identical as far as a compiler is concerned; @tim.holy’s point is that because Julia’s dispatch is capable of occurring at runtime (when types are not fully resolved) we call the whole thing multiple dispatch - a point that I accept - with the addition that it would be helpful if examples emphasising Julia’s multiple dispatch had items where types are dispatched at runtime to clearly distinguish it from overloading.

2 Likes

What about Stefan Karpinski’s examples showing differences in behaviour between overloading and multiple dispatch, separately from compilation optimizations. I’m curious to hear your take on those.

Your definition doesn’t agree with the industry-standard meaning of the term. You can see that it’s incorrect without even having to consider multiple arguments or Julia code—it’s wrong already for single dispatch in C++, Java, C#, etc. Let’s consider C++, which makes this particularly easy to show because the virtual keyword on methods allows choosing between static (non-virtual) and dynamic (virtual) dispatch semantics. Here’s an example program:

#include <iostream>
using namespace std;

class Pet {
    public:
        string name;

        string common_name() { return "pet"; }
        virtual string latin_name() { return "animalia"; };

        virtual void describe() {
            string c = common_name();
            string l = latin_name();
            cout << name << " is a " << c << " (" << l << ")" << endl;
        }
};

class Dog : public Pet {
    public:
        string common_name() { return "dog"; }
        virtual string latin_name() { return "canis lupus familiaris"; }
};
class Cat : public Pet {
    public:
        string common_name() { return "cat"; }
        virtual string latin_name() { return "felis catus"; }
};

int main() {
    Dog fido;  fido.name  = "Fido";
    Cat felix; felix.name = "Felix";

    fido.describe();
    felix.describe();

    return 0;
}

If you compile and run this program, here’s what it prints:

Fido is a pet (canis lupus familiaris)
Felix is a pet (felis catus)

The common_name method is statically dispatched because it is non-virtual and the latin_name method is dynamically dispatched because it is virtual:

  • common_name() is “pet” for both pets because the method is selected solely based on the static type of this in the body of describe, which is always Pet.

  • latin_name() is “canis lupus familiaris” for a Dog and “felis catus” for a Cat because the method is selected based on the runtime type of this.

By your definition, these would both be static dispatch since the compiler can easily see that fido is a Dog and felix is a Cat and doesn’t need to actually do any runtime method selection. However, that disagrees with what people in the industry would call this: the non-virtual one is static dispatch (aka “early binding”) and the virtual one is dynamic dispatch (aka “late binding”). It doesn’t matter at all if the compiler actually does a runtime method lookup or not—the behaviors are different, which is what distinguishes them.

Whether dispatch is dynamic or static is a matter of language semantics (behavior), not implementation. It doesn’t matter when or how the actual method lookup is done: static dispatch dictates that one function body is called whereas dynamic dispatch dictates that a different function body is called. It doesn’t matter how it’s implemented, so if you’re looking at the implementation to see if the dispatch is actually done at runtime or not, you’re barking up the wrong tree.

In Julia there is only dynamic dispatch: there is no way to ask for static (non-virtual) dispatch behavior—it is all dynamic, all the time, on all of the arguments. There isn’t even a concept a static type by which one might statically select a method in principle. The fact that the compiler is very good at statically figuring out which method to call is fortunate but irrelevant.

32 Likes

Well, multiple dispatch as defined in the Julia manual is distinguished by dispatch on the types of all arguments. This is in contrast to dispatch based on the type of the first argument only.

You insist on conflating the meaning of this with dynamic versus static dispatch. I believe that both multiple dispatch and single dispatch as defined above can be both dynamic or static. C++ vs Python, if you will.

2 Likes

I have an extra question, to see if I can get your position straight.

I’m not sure about what the current state of the Julia interpreter is, but are you saying that if I start Julia in ‘interpreter mode’, I’m seeing multiple dispatch, but if I run it in ‘jit mode’ Julia loses its multiple dispatch? The behaviour is irrelevant?

What is the usefulness of this distinction?

2 Likes