20 comments

[ 2.7 ms ] story [ 38.7 ms ] thread
Is this an example of what Mr. Bendersky is talking about? I believe so:

https://github.com/perl6/book/blob/master/src/multi-dispatch...

No. That is overloading with single dispatch. You need multiple arguments for multiple dispatch (they can be implicit, as with the 'this' argument in C++, but they rarely, if ever, are because making one argument implicit makes it look as if the situation is asymmetric in the arguments, and that is exactly something multiple dispatch is not)

Edit: oops. I didn't notice that the discussion went beyond the to-json example, which definitely is _not_ multiple dispatch. The discussion starting at the header Multiple arguments is about multiple dispatch.

Perl 6 dispatches on multiple arguments based on runtime types, so it's full multiple dispatch.
I thought that, while Perl 6 can dispatch based on runtime types, it does candidate resolution at compile-time for functions (and thus operators) and private methods. It only adds in run-time overhead for these cases if the code uses a run-time type construct. Am I wrong?
Ooh Python! Check out https://github.com/mrocklin/multipledispatch which is a little more fleshed out than Guido's implementation. From the author of toolz and dask so you know it's good ;)

I also find that multiple dispatch is something that's occasionally handy, but single dispatch is truly everywhere. I think if you broke down multipledispatch uses in languages which embrace it (e.g. CLOS), the mix is like 95% single, 5% >single. For single dispatch in python, check out https://docs.python.org/3/library/functools.html#functools.s... which was added in to the Python 3.4 stdlib. The alternative is the OOP subclassing style, which works but IMHO isn't very readable.

MD in Perl 6 is beyond "occasionally handy". A glance at the Perl 6 setting modules[1] revealed something like a 50/50 break down of SD vs MD.

Operators in Perl 6 are all MD, so that's likely a significant factor.

Additionally it is well designed. While the functionality starting point was CLOS, the UX has the benefit of hindsight and a decade of non-stop language evolution.

[1] https://github.com/rakudo/rakudo/tree/nom/src/core

It a bit funny that the article doesn't mention Julia, which has native multimethods with all kinds of bells and whistles. It could be a nice addition to a "polyglot" flavored discussion. There is also Clojure, although its mechanism for multiple dispatch is a bit overpowered (you dispatch on values returned from a function of the arguments).
Part 3 will talk about Common Lisp / CLOS which had multiple dispatch 20 years before Julia was conceived :-)

Part 4 will talk about Clojure.

I just couldn't put it all in one piece - even broken to chunks it's of formidable length

What do you think of Julia?
I don't think of Julia, to be honest
Common Lisp has certainly had multiple dispatch for a long time. One important distinction, however, is that in CL generic functions are opt-in rather than the default. This means that most operations are not generic, limiting the extent to which you can extend things – basically only when some library author decided that you should be allowed to. Dylan and Julia both have functions which are generic by default, meaning that almost all functions can be extended.

Another distinction is that while Common Lisp and Julia both have parametric types, in CL you cannot use multiple dispatch on parametric types. Since parametric types are a very useful for writing generic, reusable libraries, not being able to dispatch on them is a significant limitation.

There's also the issue of performance. In Julia even something as basic as mixed-type addition – e.g. `1 + 2.5` – is defined in terms of layers of generic functions, but the compiler is clever enough to boil it down to a couple of machine instructions. This allows multiple dispatch to be used everywhere, which is really transformative. I'm not aware of any CL implementation which can eliminate the abstraction overhead of multimethods so completely.

It seems a bit remiss to have a series on multiple dispatch and never mention the two languages – Dylan and Julia – that take the paradigm the furthest and most seriously.

> Dylan and Julia both have functions which are generic by default

And Perl 6.

> in CL you cannot use multiple dispatch on parametric types.

You can in Perl 6.

> In Julia even something as basic as mixed-type addition – e.g. `1 + 2.5` – is defined in terms of layers of generic functions, but the compiler is clever enough to boil it down to a couple of machine instructions. This allows multiple dispatch to be used everywhere, which is really transformative.

MD has been used everywhere in Perl 6.

Perl 6 has maturity issues, including performance. But I see these as temporary issues.

> It seems a bit remiss to have a series on multiple dispatch and never mention the two languages – Dylan and Julia – that take the paradigm the furthest and most seriously.

Are you omitting Perl 6 because you don't think it's ready for prime time or you don't know enough about it's MD features?

------

I note that the OP series is about "runtime subtype-based polymorphism". I'm surprised at the exclusion of compile-time MD in a series which purports to be a general guide.

> Are you omitting Perl 6 because you don't think it's ready for prime time or you don't know enough about it's MD features?

Both. Are basic operations like `+` generic in Perl 6?

> Are basic operations like `+` generic in Perl 6?

Yes.

Well, that's cool. Maybe Perl 6 will be the next good example of a multiple dispatch language.
I think I misunderstood your comment about CL vs Julia/Dylan in regard to generic "by default".

In Perl 6 this function definition implies a generic function:

    multi concatenate (\foo, \bar) { foo ~ bar }
This function definition does not:

    sub concatenate (\foo, \bar) { foo ~ bar }
If one views the latter as the "default" (because it's two characters shorter and might be learned first) then Perl 6 has the same weakness, or at least the same default, as CL, compared to Julia and Dylan.
> however, is that in CL generic functions are opt-in rather than the default

In the default standard this might be true to some extend. We are talking about ANSI Common Lisp here. Though ANSI CL has several places where CLOS comes into play. In real implementations it's not true at all. ANSI Common Lisp is a minimum standard (even though it is large) and implementations are free to extend the use of CLOS - which they do.

Even though Dylan had the goal of efficient CLOS-like OOP, the Dylan spec was not very detailed about that - there were individual implementations. But how fast were they really? Difficult to say: Apple Dylan was only published as a technology demo. Harlequin Dylan was short-lived.

Julia OTOH is a language and its implementation. It brings some good ideas how to make OOP performing better. If you compare Julia with CL implementations like CCL, LispWorks, SBCL or Allegro CL - you will find that CLOS is widely used there. Much more widely than the ANSI CL standard defines. For example the base LispWorks on my Mac implements 1600 classes. It has 32000 functions of which 5182 are generic functions - and 10000+ methods.

So claiming that Common Lisp implementations are mostly not extensible via CLOS is not giving the whole picture.

I/O Streams are not CLOS based. In most/many implementations they are. There are one or two quasi-standards for that: Gray streams is one.

Conditions might be CLOS based. In most implementations they are.

CLOS is somewhat CLOS based. In most implementations it is fully CLOS-based with a meta-object Protocol.

There is a minimum ANSI CL, which makes it possible that individual implementations are mostly CLOS based. Then there is a larger Common Lisp, which assumes CLOS-based error handling, streams, CLOS itself, CLOS-based networking, CLOS based software management (ASDF).

Dylan and CLOS had different goals. Dylan was developed for application development where delivery and performance was focus. CLOS focus was on flexibility, expressiveness, extensibility, meta-object protocols, etc etc.. Performance was a topic in two ways: First: it should be possible to write large extensible libraries and applications in CLOS (like the typical GUI or networking code). Second: for applications that use CLOS for more performance critical things (like a CAD system with zillions of CLOS objects describing a 3D model) it was assumed that individual implementations provide additional features (like sealing, efficient slot access, etc.).

Writing high-performance numeric code (or similar) in a CLOS based way was not really thought of. It's definitely useful to extend higher-performance implementations with an optimizing compiler (like SBCL, ACL, LispWorks, ...) so that more performance critical code can be written in CLOS. But keep in mind that not all CL implementations have this goal of highest OOP performance. Many have other goals and already lack the attempt to provide an SSC.

SSC = sufficiently smart compiler

Although a bit less interesting than part 1 IMO, I think it's still a really good read, especially the point:

    However, as we've seen here there are several variations
    on the multiple dispatch scheme, and each requires
    slightly different implementation considerations.
This is interesting, and probably pretty important, especially if you're considering using Multiple Dispatch (MD) in a code base yourself. One of the advantages of CLOS-style MD is that it is built in, and usually covers your MD needs for 99%+ of cases (from what I've seen, at least). Looking forward to part 3 next week Eli!