HN Theater @HNTheaterMonth

The best talks and videos of Hacker News.

Hacker News Comments on
JuliaCon 2019 | The Unreasonable Effectiveness of Multiple Dispatch | Stefan Karpinski

The Julia Programming Language · Youtube · 22 HN points · 34 HN comments
HN Theater has aggregated all Hacker News stories and comments that mention The Julia Programming Language's video "JuliaCon 2019 | The Unreasonable Effectiveness of Multiple Dispatch | Stefan Karpinski".
Youtube Summary
If you're familiar with Julia and its ecosystem, you may have noticed something lovely but a bit puzzling: there seems to be an unusually large amount of code reuse between packages compared to other seemingly similar languages. This sharing of code comes in two forms:

1. Sharing basic types among a wide variety of packages providing disparate functionality;
2. Sharing generic algorithms that work on various implementations of common abstractions.

Why does generic code in Julia "just work"? Why do Julia packages seem to share types with so little friction? Both kinds of reuse are supposed to be natural benefits of class-based object-oriented languages. After all, inheritance and encapsulation are two of the four pillars of OOP. Even more puzzling is that Julia has no encapsulation and doesn't allow inheriting from concrete types at all. Yet both kinds of code reuse are rampant. What is going on? In this talk, I make the case that both of kinds sharing stem directly from Julia's multiple dispatch programming paradigm.
HN Theater Rankings

Hacker News Stories and Comments

All the comments and stories posted to Hacker News that reference this video.
Aug 19, 2022 · krastanov on Julia 1.8
A couple of things that made a difference for me:

- I actually like vectorized code, I find it easier to read, but especially in python, vectorized code still has some fixed overhead that can be prohibitive for smallish vectors.

- Some of the numerical code I run is not easy to batch, so tools like tensorflow are cumbersome. With Julia it is easy to make fast non-allocating code while writing in easy to read style like in Python. In Python you need to use C/Cython/Numba to achieve this, but then you lose interoperability with other Python packages. E.g. auto-diff through fast cython code does not work, while autodiff through my equally fast non-batched Julia code is trivial.

- Ecosystem: I work with differential equations a lot. Julia's SciML library (and its DifferentialEquations.jl sub-library) is light years ahead compared to any other tool in Python/R/Mathematica/Matlab/Maple. Speaking of python, solving differential equations in python feels like I am using technology that is quarter of a century old.

- In python I might write a nice and legible slowish implementation of something and then piece by piece I will start replacing it with cython. That requires a lot of boilerplate. In Julia I just write the slowish implementation with the same ease with which I would do it in python, but then I need to perform extremely minimal profile-guided changes with zero boilerplate to achieve the cython/numba type of performance. And again, I do not lose interoperability with other parts of the ecosystem when I do that.

- It is trivial to inspect the actual machine code that was generated for every single function you write. It is trivial to inspect exactly where allocations happen. Most of the ecosystem is in pure Julia, so it is trivial to edit fairly foundational pieces of the ecosystem and submit patches. On the other hand, the levels of abstraction in something like Jax/Tensorflow/Numba makes it relatively difficult to contribute.

- It is wild how much code reuse you can have with the multiple dispatch paradigm. Say I find someone's pet project on performing a complicated linear algebra algorithm. That code was probably written just to work with Float64, but thanks to multiple dispatch the compiler can generate specializations for esoteric higher-precision real number representations, or real numbers with error bars, or unitful numbers. I still need to do some sanity checks, because hidden assumptions might be problematic, but this level of interoperability is possible only with multiple dispatch (thinking of it as one of the "standard solutions" for the Expression Problem[1] really helped; [2] was also informative). I think the GPU libraries for Julia are a good example of multiple dispatch working well in that sense.

[1]: https://en.wikipedia.org/wiki/Expression_problem [2]: https://www.youtube.com/watch?v=kc9HwsxE1OY

Yes. Its a side effect of multiple dispatch being the core paradigm of the language. See Stefan Karpinski's talk about it: https://www.youtube.com/watch?v=kc9HwsxE1OY
FabHK
The title of Stefan's talk is great: The Unreasonable Effectiveness of Multiple Dispatch. He gives a nice example of composability: how you can throw a new type into an existing algorithm and it just works.
Well respectfully, you provided the original project spec as an example of where objects were required, and I demonstrated that no, they mostly weren't. I agree that it's unproductive for you to keep adding things to the spec so that I can demonstrate that nope, you still don't need objects to do all that.

All that objects are, are functions glued to data. That's it. Whether or not you choose to glue them together, the behavior of the dog lies in the functions, and the state of the dog lies in the data. Frankly, your examples seem to lead away from objects - how will another developer add the ability to heal dogs, for instance? They can't change the class definition, so they'll have to do something awful like subclass it into HealableDog(), which doesn't interoperate with all the existing dog code at all. Meanwhile, if 'dog' is simply a big ol' struct of data, they can write a function heal(dog) and call it a day.

I highly recommend the first part of this video, which explains the composability problems with objects very well - with dogs and cats even! https://www.youtube.com/watch?v=kc9HwsxE1OY

throwaway889900
Make Dog implement Healable, duh.
dTal
The original author can do that. Another author cannot add that functionality later, as a library. The can only define a new kind of dog.
throwaway889900
Subclass Dog into MyDog, which is exactly the same as Dog. From there, use and modify MyDog as though you would be modifying Dog. Any class that references base class Dog isn't modifiable, but any future work you can use MyDog instances to get all the functionality you need (with some ugly casting). There are other solutions as well.
ozim
Link to YouTube was really nice.

I would not agree that one cannot change the class definition as it really depends on the system one is writing. I change class definitions on daily basis in business line applications. If I would be making frameworks then maybe I would not do that so often.

Java falls apart on the point about post factor extensibility.

See this talk for examples: https://www.youtube.com/watch?v=kc9HwsxE1OY

Oct 30, 2021 · 5 points, 0 comments · submitted by behnamoh
In Julia it will just dispatch to the correct function.

In other words, one package would define `fit(mymodel::TensorFlowModel)` and the other would define `fit(mymodel:PyTorchModel)`, and then when you call `fit` it'll just dispatch to the appropriate one depending on the type of `mymodel`.

This dispatch-oriented style also allows a shocking degree of composability, e.g. [1], where a lot of packages will just work together, such that you could for example just use the equivalent of PyTorch or TensorFlow on the equivalent of (e.g.) NumPy arrays without having to convert anything.

If you mean "what about the case where both packages just call their model type `Model`", while I've never run into that, the worst case scenario is just that you have to fall back to the Python style explicit Module.function usage (which was always allowed anyways...). And if you if you don't like names being exported, you can always just `import` a package instead of `using` it:

  help?> import
  search: import

  import

  import Foo will load the module or package Foo. Names from the imported Foo module can
  be accessed with dot syntax (e.g. Foo.foo to access the name foo). See the manual
  section about modules for details.

[1] https://www.youtube.com/watch?v=kc9HwsxE1OY
awaythrowact
I very frequently run into namespace collisions like that. I think they are quite common in large codebases.

I am aware of the ability to do eg "import TensorFlow; model = TensorFlow.model; TensorFlow.fit(model,data)"

As I mentioned previously, I find Python's OOP "model.fit" syntax to be better, for a variety of reasons.

Thank you for your engagement. Have a nice day.

cbkeller
Do you have an example of a case where you ran into this in Julia with two packages that you wanted to use together? If the packages are still actively developed, I suspect the developers would be interested to resolve the situation to allow interop.
awaythrowact
Never made it that far. This was a feature I use all the time in Python ML development (both consuming open source packages via an OOP interface and also writing in-house model classes) that I consider essential for my productivity and that Julia was missing.

<edit>I'm also nervous of relying on the recourse of asking package maintainers to edit their variable names to improve compatibility with the random third package I want to use; maybe the culture is different in Julia but in Python that's a good way to get laughed out of the issue tracker :) </edit>

If I were you I would maybe ask people like @systemvoltage who took the plunge and wrote a big project in Julia only to find they had trouble maintaining the project. Maybe one reason he can't upgrade without breaking everything is because of namespace collisions amongst his many dependencies? If not that, it's something like that.

cbkeller
Composability is one of the things the Julia community generally Takes Seriouslyᵀᴹ so definitely don't hesitate to ask if there are two packages that don't play as nicely as you would like!

I'm a bit confused still though why you say it's a "missing" feature, given that as we discussed above, there is absolutely nothing to stop anyone who wants to use the "Python OOP" style of namespacing in Julia from doing so? Most of us don't seem to find it necessary or to prefer it personally, but that doesn't restrict anyone else from choosing it.

awaythrowact
@cbkeller

I know in Julia I can just precede every function call and object instantiation with "modulename." and solve the namespace problem that way. What I want to do instead, what I do in python, is bind one namespace of function methods to each object, so that as I code, I don't have to remember which module each each object came from. That is the appeal to me of "model.fit" over "module.fit(model)".

EDIT: This is not some "shave a few seconds off coding time" quality of life issue. This is a mission critical requirements in many enterprise contexts.

Scenario A: Model Development Team trains a model, serializes it, and sends the serialized model object to Production. You want Production to have to lookup which software Package the Model Development Team used for each model, just so they know which "predict" function to call? No, "predict" should just be packaged with the model object as a method, along with "retrain", "diagnose", etc.

Scenario B: I want to fit a dozen different types of models, from multiple packages, on the same data, to evaluate how they each do, and build build some sort of ensemble meta model. So I need to loop over each model object in my complicated ensembling logic. You want me to also programmatically keep track of which module each model comes from?

These are important, happens-every-day use cases in the enterprise.

I think the best solution for this in Julia is to package the model state with all the "method" functions in one struct. Again, this is what MLJ does. This is the closest Julia gets to OOP. But then you either need a lot of boilerplate code, or a Macro to unpack it all for you behind the scenes.

cbkeller
Oh, I see -- yes, fair enough!
awaythrowact
Just to be clear, I like Julia, and think it has advantages over Python. I'm writing all this as someone who is cheering for Julia to break out of its HPC niche. Thanks.
amkkma
Thanks so much for taking the time to outline your thoughts...I share the same goals and input from industry experience like this very valuable. This has spawned some discussion on the Julia slack about how best to target your usecase.

Can I trouble you to make a post either on discourse or on the slack? I'd really like this to get in front of the broader julia community and core devs, and you're the best person to do that. Maybe there's a solution of which I'm unaware....or there could be some design discussions to come out of this.

https://julialang.org/community/ (slack and discourse link)

awaythrowact
Always happy to help. Especially the last day or so - I've been waiting on some long training loops so it's been a pleasant diversion.

To be 100% honest with you, there's pretty much 0% chance of me adopting Julia in the next 12 months. I evaluated it before embarking on my current project, but ended up going with Python, and now I have several thousand lines of Python code that work fine, and I'm not going to rewrite it all in the near future. At some point in the medium term, I'll re-evaluate Julia. But until then, I don't want to lead anyone on any wild goose chase. Even if you solved this problem, and all my problems, I'm just not in a position to switch right now. So for that reason I think I'll hold off on issuing a community wide call for help. But I'm cautiously optimistic that at some point in the future I'll be writing Julia professionally.

A lot of this is also probably cultural rather than language features. The first thing they teach any Python data science boot camp initiate is: never "from numpy import *", always "import numpy as np" and yet in Julia "using" appears more common than "import"...

I also wouldn't read too much into my one example. It was initially meant just as an illustrative point, but somehow I was so bad at explaining it that it took tons of comments for me to get my minor point across. I do think the MLJ guys are properly on the right track, and that should work fine for most people who don't mind Macros. Maybe I'm in the minority in hating Macros.

The more commonly cited issues around compile time caching, tooling, etc. are boring to list off yet again, but probably the right areas of focus for the community, in terms of both impact and ease of implementation.

More generally, I really do think you're better off talking to people like @systemvoltage, who have actually given Julia more of a chance than I have. If I worked for Julia Computing I'd be reaching out to him and begging to get his brain dump on all the challenges he faced. In any business, it's always easier to reduce churn and learn from your (unhappy) customers, then it is to convert new prospects, whether that business is programming languages or investment banking.

Best of luck. Sincerely.

dklend122
That makes sense, thanks ! Good luck to you as well.
xiaodai
hmm, but doesn't it just mean that ppl should extend the same `fit!` method rather the define their own?

The bigger issue for production in my experience is about packaging the right model with the right version. I don't think anyone has to do `module1.fit` everywhere, since `fit` would've likely come from the same source.

That solves both of your scenarios

awaythrowact
> since `fit` would've likely come from the same source

No.

As described in the great-great-great-great-great-great-great-great-great-parent comment, the problem I am describing is that of trying to combine models from multiple software authors. You may not have that problem. It may not be a common problem among Julia's academic user base. I do have that problem.

Thanks for reading.

DNF2
There's some serious misunderstanding here. You do not have to disambiguate the function call, only the construction of the object. You would write

  m1 = TensorFlow.model()
  fit(m1, data)
  m2 = Pytorch.model()
  fit(m2, data) 
Julia knows which version of model you are using.

YensorFlow.fit and Pytorch.fit are just different methods of the same function.

You've formed some strong opinions based on a pretty big misunderstanding.

awaythrowact
Literally the entire point of the thought exercise is if there's a namespace collision on the model subtype, because sometimes in the real world developers don't coordinate, and don't prepend all their variable names with their package name.

I do not believe I am imagining this problem out of thin air. See eg: https://discourse.julialang.org/t/function-name-conflict-adl...

Or see the helpful advice, "let this be a reminder to package devs not to use extremely generic names for functions" https://discourse.julialang.org/t/how-do-people-handle-name-...

The AllenAI folks are very smart people who think a LOT about software engineering for ML. If they decided the "pass fit() as a variable in a struct and use Macros to load the right fit() into global namespace" was the least bad way to implement a SciKit style API in Julia, I think they are probably right, and nothing in this thread so far has convinced me they are wrong.

There was a comment up-thread about the Julia community not being open to feedback and I think my experience in this conversation has further cemented that impression.

Good day all. Happy coding.

Composability via dispatch-oriented programming, e.g. [1]

It also pretty much solved my version of the two-language problem, but that means different things to different people so ymmv.

[1] https://www.youtube.com/watch?v=kc9HwsxE1OY

Jul 07, 2021 · 1 points, 0 comments · submitted by amkkma
Apr 13, 2021 · 2 points, 0 comments · submitted by 323454
Mar 25, 2021 · ddragon on Julia 1.6 Highlights
This video is good explaining the idea behind multiple dispatch in Julia if you have time:

https://www.youtube.com/watch?v=kc9HwsxE1OY

I think people often underestimate (or just plain don't know about) the degree to which a multiple-dispatch-based programming language like Julia effectively implies its whole own dispatch-oriented programming paradigm, with both some amazing advantages (composability [1], and an IMO excellent balance of speed and interactivity when combined with JAOT compilation), but also some entirely new pitfalls to watch out for (particularly, type-instability [2,3]). Meanwhile, some habits and code patterns that may be seen as "best practices" in Python, Matlab can be detrimental and lead to excess allocations in Julia [4], so it may almost be easier to switch to Julia (and get good performance from day 1) if you are coming from a language like C where you are used to thinking about allocations, in-place methods, and loops being fast.

Things are definitely stabilizing a bit post-1.0, but it's still a young language, so it'll take a while for documentation to fully catch up; in the meanwhile, the best option in my experience has been to lurk the various chat forums (slack/zulip/etc. [5]) and pick up best-practices from the folks on the cutting edge by osmosis.

[1] https://www.youtube.com/watch?v=kc9HwsxE1OY

[2] https://www.johnmyleswhite.com/notebook/2013/12/06/writing-t...

[3] https://docs.julialang.org/en/v1.5/manual/performance-tips/#...

[4] https://github.com/brenhinkeller/JuliaAdviceForMatlabProgram...

[5] https://julialang.org/community/#official_channels

I think it really depends on what subset of data science you have in mind. For me it solved my personal version of the "two-language" problem, but YMMV. Longer-term I think the most interesting thing about the Julia ecosystem is the composability that comes from dispatch-oriented programming [1]

[1] https://www.youtube.com/watch?v=kc9HwsxE1OY

Julia's model is based around multimethods, which enables pretty crazy levels of interoperability (imagine being able to use scipy and tensorflow together, with tensorflow's autograd just magically working on most of scipy's functions). More about it here https://m.youtube.com/watch?v=kc9HwsxE1OY
Feb 04, 2021 · 4 points, 1 comments · submitted by O_H_E
O_H_E
Similar write-ups for those who would rather read, but I strongly encourage you to listen to the talk:

(Simple and easy) https://medium.com/swlh/how-julia-uses-multiple-dispatch-to-...

(Deeper) https://opensourc.es/blog/basics-multiple-dispatch/

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.

[1] https://www.youtube.com/watch?v=kc9HwsxE1OY

Looks like a decent basic summary. Section G.2.6 should probably be called "Multiple Dispatch" rather than "Function Overloading" though. Defining multiple methods of the same function in Julia is different to function overloading in a language like C++ [1].

[1] https://www.youtube.com/watch?v=kc9HwsxE1OY&t=392s

east2west
Perhaps the author was trying to make the overview beginner-friendly by using familiar terms. I have found it helpful to compare and contrast Julia concepts with C++ template concepts. For example, term trait is used in similar ways in both languages, but the big difference is that in Julia type information can be passed in run-time, not just in compile-time.
"The Unreasonable Effectiveness of X" is a reference to the well-known 'the unreasonable effectiveness of mathematics in the natural sciences'. It's turned into some sort of meme. Maybe you've seen 'the unreasonable effectiveness of recurrent neural networks' before.

What you were referring to is a talk by a core developer at JuliaCon 2019 titled 'The unreasonable effectiveness of multiple dispatch' [1], which has less of a fanboy-vibe imho.

Edit: Oh. I didn't notice the arstechnica article had this exact title. That's a bit cheap...

[1] https://www.youtube.com/watch?v=kc9HwsxE1OY

StefanKarpinski
I suspect that the title of the Ars Technica article was a reference to the talk title, which was a reference to many other pieces with similar titles, which are all ultimately references to the original one.
What? I used to write python, and to me, MD is first and foremost a conceptual improvement. Makes writing code so much sweeter, more expressive and sensical.

It also heavily improves composability.

It's LESS performant if not done carefully. Certainly FastAI, Matt rocklin etc didn't reimplement a subset in python just to get slower code

See https://youtu.be/kc9HwsxE1OY

DNF2
I agree, the performance is a very nice bonus, but it is primarily about expressiveness, composability and natural simplicity.

It just seems profoundly un-natural and arbitrary to limit dispatch to only the first argument.

The core problem with class-based object orientation is that methods go inside of the type instead of being added externally. That means everyone has to agree on what methods can be called on a class or they have to subtype it, which sounds harmless, but is actually a big problem when what you really wanted to do was just define a new method for an existing type. My best attempt at explaining: https://www.youtube.com/watch?v=kc9HwsxE1OY

There's also the issue that one sometimes needs to specialize generic operations in generic algorithms on something other than the receiver—sometimes even on more than one of the arguments. Single dispatch forces you to use something slow and awkward like double dispatch in cases like this. And that's assuming the person who wrote the generic code anticipates the need for specialization! If they don't allow for it, then you're just stuck. With multiple dispatch, you can just define the method you need the specialization for and you're done.

For a more technical description, I don't think there's a better source than Stefan Karpinski's 2019 JuliaCon talk "The Unreasonable Effectiveness of Multiple Dispatch".

https://www.youtube.com/watch?v=kc9HwsxE1OY

I like to watch talks by programming language designers. Some that readily come to mind are Rich Hickey, Joe Armstrong (RIP), and Stefan Karpinski.

Some of my favorites are:

- Simple Made Easy (Hickey): https://www.youtube.com/watch?v=oytL881p-nQ

- The Mess We're In (Armstrong): https://www.youtube.com/watch?v=lKXe3HUG2l4

- The Unreasonable Effectiveness of Multiple Dispatch (Karpinski): https://www.youtube.com/watch?v=kc9HwsxE1OY

Sep 02, 2020 · GistNoesis on In Defense of a Switch
I like Julia's solution to it : Multiple dispatch. It's great for code reuse and generic algorithms, but it kind of need the magic of the Julia compiler to make it shine.

JuliaCon 2019 | The Unreasonable Effectiveness of Multiple Dispatch | Stefan Karpinski https://www.youtube.com/watch?v=kc9HwsxE1OY

ahefner
As a Common Lisp head, I love generic functions / multiple dispatch and would take them even over classes and inheritance as a language feature, but it's definitely cheating when the expression problem is framed in terms of static type systems where your code is guaranteed not to fall over at runtime with a 'no applicable method' error'.

(...although I could have gotten a lot of useful work done with my dirty dynamic types and shameful runtime errors in all the years people searched for a solution to the expression problem...)

wodenokoto
Doesn't R also use multiple dispatch[1]? What magic does Julia bring to the game that R doesn't?

[1] http://adv-r.had.co.nz/S4.html

GistNoesis
Julia makes it a zero-cost abstraction by emitting and optimizing the code dynamically.
amkkma
The type system. Also making it a zero cost abstraction as mentioned by the other child comment. Type system and language semantics are critical for that as well, not just the compiler.
smabie
Julia's function polymorphism shines in a lot of cases, but in general it is less powerful than the OOP solution and also I find it much more difficult to read.

For example, I recently ran into this problem when prototyping a new Julia library. Without going too into the specifics, I can only match on a type of a object. With Scala say, I can override the method of an object at creation without actually defining a class.

In order to do a similar thing with Julia, I have to package the function as a field of the struct, I can't do multiple dispatch on it.

In general I think multiple dispatch is great for the incremental adding of new functionality without modifying the original source but bad at dynamically augmenting functionality at runtime.

A common example in Scala is creating an iterator. I can override the next and hasNext methods at object creation instead of creating a whole new class with a name. With Julia I would have to define a new kind of iterator at the top-level and then implement the necessary functions.

Let's say I want to have an iterator type of numbers in which I want to multiply or add constants to. In Scala I can easily return a new iterator that wraps the old one with overridden methods that adds or multiples by the constant. I could write

  val newIter = 2 + iter
Or whatever and with the right definition of +, everything would work correctly. In Julia I belive the only way to make this work would be to package the next and hasNext functions (or their Julia equivalents) inside the struct itself, which is unidiomatic I believe.

Also the amount of type-piracy can get a little annoying. A lot of libraries end up defining alternative dispatches that are inappropriate.

For Julia's problem domain, I do think this approach makes more sense than OOP and leads to saner scientific code. A lot of Python scientific computing libraries have these crazy baroque mutation heavy brain dead OOP interfaces that serve no purpose. It's just that in some cases, the packaging of data and functions together is really the only way to sanely solve a given problem.

snicker7
Your example is specific to not all OOP languages, only those that have prototypical inheritance.
ChrisRackauckas
>Julia's function polymorphism shines in a lot of cases, but in general it is less powerful than the OOP solution and also I find it much more difficult to read.

Given that OOP is single dispatch and multiple dispatch is a strict generalization, that's not just wrong or an opinion but it's a statement that can't be true. a.b(x) is equivalent to b(a,x) where b is a (possibly callbale struct) function you allow dispatching on a but not x. Multiple dispatch is the case where you can, well, dispatch on both.

>Without going too into the specifics, I can only match on a type of a object. With Scala say, I can override the method of an object at creation without actually defining a class.

You could also just drop to Val to dispatch on values or even define calls from the parameters like GeneralizedGenerated. That's pretty much the behavior you're trying to recreate so that would just do it directly. It's not the most common thing to do since you will hit dynamic dispatch, but of course if you're trying to dynamically add or change functions that's bound to happen.

>Also the amount of type-piracy can get a little annoying. A lot of libraries end up defining alternative dispatches that are inappropriate.

Interesting. Can you please make a list?

The grandparent meant that OCaml is static while Julia is dynamic, and both are strongly typed.

>You can specify types, but they don't really do anything except help with performance.

For Julia it's the exact opposite though, types don't help with performance since the compiler infers them anyway regardless of declaring them or not (you only really should to specify in very particular cases where inference is not possible to avoid the compiler being too conservative, and if you overspecify types you might even end up with a slower program). You declare them for their main purpose of controlling dispatch.

Julia's type system is a core aspect of it's paradigm (multiple dispatch [1]), which is the key element in both it's performance and polymorphism. It's different from a gradual typed language in which the language can exist without the types at compilation time, but those can be optionally added to enhance safety or performance, Julia cannot be compiled without knowing the types, which will happen regardless of declaration.

[1] https://www.youtube.com/watch?v=kc9HwsxE1OY

That said, types in Julia are not used for compile time static checking, which is a compromise that for many tasks it's not worth it. Ocaml ML and Julia ML can coexist well exactly because they have vastly different compromises, with Julia focused on interactivity/fast prototyping and Ocaml in safety (like Lisp x Haskell, but with somewhat more pragmatic languages).

Relevant presentation by one of the creators of Julia: https://www.youtube.com/watch?v=kc9HwsxE1OY
It's more of a paradigm than a feature but, multiple dispatch à la Julia. One of the creators of Julia makes a strong case that it is the key to libraries that "compose" without being aware of one another:

https://www.youtube.com/watch?v=kc9HwsxE1OY

Foober223
They got that from lisp.
bjoli
Julia compensates for time lost by making a lot more use of it in the stdlib than, say, most CL implementations do. It is as if the julia people saw it and went "wow, this is good!" and had it in from the beginning. With their focus on maths, it is hardly surprising.
eigenspace
There's actually a pretty fundamental reason that Julia's Base and stdlib makes more use of multiple dispatch than Common Lisp (or any other language in existence with multiple dispatch). It's that in basically every other language, multiple dispatch comes with a non-neglible performance penalty.

Julia's multiple dispatch semantics were designed around having a JIT compiler, and it's JIT compiler design was designed around having multiple dispatch, and this tight integration let us be really good at de-virtualizing the dispatch, removing the hefty performance pentalty.

This is why even basic arithmetic operations like +, * and whatnot are able to be generic functions (with a gigantic number of methods, 184 for + and 364 for * currently just in Base and LinearAlgebra!) without any runtime performance compromise.

By contrast, Nim for instance just removed multiple dispatch in their 1.0 release because nobody was using it due to being so slow.

bjoli
Thanks! I always suspected you could have a fast dynamic dispatch in a typed language if you locked it down enough to not allow redefinitions at runtime and such (which removes a lot of the fun of CLOS). It makes sense to let the jit do the heavy lifting though!
eigenspace
Yes, Julia definitely had to make some dynamism restrictions that I suspect would dismay some lispers, but it is still a quite dynamic language. The language devs do some pretty clever things to have our cake and eat it too.

On the flip side, I think we have some dynamic metaprogramming magic of our own that might even impress some lispers such as IRTools.jl's dynamos or Cassette.jl's overdubing.

eigenspace
Multiple dispatch is a wonderful thing. It's basically what OOP should have been, rather than the horrible cludge of classes.
Jul 05, 2020 · ssivark on Multiple Dispatch in Julia
You might also enjoy Stefan Karpinski’s talk at JuliaCon 2019 about how multiple dispatch helps make the language more expressive (than OOP, for Eg). The unreasonable effectiveness of multiple dispatch https://youtu.be/kc9HwsxE1OY

It seems like such an elegant idea, I’m somewhat surprised no one seems to have run with it earlier.

kkylin
I don't think Julia is (or has ever claimed to be) the first system to make extensive use of multiple dispatch. But it seems to be first one to have gotten attention, if not widespread adoption, from the broader community beyond its first intended audience, i.e., scientific computing.

Others mention CLOS, which I don't know too much about. MIT Scheme also supports multiple dispatch (https://www.gnu.org/software/mit-scheme/documentation/stable...). IIRC this is widely used in ScmUtils, the software library used in SICM (https://mitpress.mit.edu/books/structure-and-interpretation-...).

None
None
ssivark
Julia’s definitely not the first, but I don’t see too many examples where they took the idea seriously enough as a design principle to build the language around it (that’s what I meant by “run with it”). I’ve seen Dylan often credited as one of the inspirations for Julia. While many other languages might support multiple dispatch, my impression is that they’re somewhat “patched on”, thereby limiting the benefits.
kkylin
Absolutely. I guess I was trying to say (not very clearly) that ScmUtils was the only other example I know of that "ran with it." Though SICM is relatively well known at this point, I suspect ScmUtils is not very widely used.
blast
Doesn't CLOS count? That was a lot earlier.
jecel
It was also the key idea in the lesser known Cecil and Slate.

https://en.wikipedia.org/wiki/Cecil_(programming_language)

https://github.com/briantrice/slate-language

eigenspace
That's an interesting point worth discussing. Julia is far from the first language to have multiple dispatch. Rather, what makes julia's take on the concept unique (as far as I know) is Julia's ability to optimize and de-virtualize generic functions.

Common Lisp had multiple dispatch through the CLOS, but functions which needed to be fast were not multiple dispatch. For instance, you can't overload multiplication or additional in Common Lisp, you instead need to shadow them and replace them with generic functions, but doing so incurs a runtime performance overhead. This is (usually) not the case in Julia because it's JIT compiler was designed around multiple dispatch and it's multiple dispatch semantics were designed around it's JIT compiler (this is a big reason why static compilation in Julia has been such a hard problem to solve!).

Because Julia can have MD without additional overhead, it's use is absolutely ubiquitous throughout the ecosystem and Julia's own internals. This ubiquity is what confers most of the benefit because you can patch into the gigantic space of method combinations just about anywhere to override behaviour.

mncharity
> I’m somewhat surprised no one seems to have run with it earlier.

Common Lisp family. Of which Apple's Dylan might have participated in the turn-of-century programming quickening... but had been cancelled. Just short of the open source language era, so that meant dead.

Also some research languages. But that used to mean "there might be an ftp tar file, and if you have a similar OS and hardware, you might be able to build, and even run it". :/

> surprised

Multi-decade waits for language tech to become available in ones "day job" language is... well, one gets used to it?

Aside from all the usual reasons language evolution is a dysfunctional disaster, here's a speculative one specific to multiple dispatch, as inferred from my conversations with language design folks through the 90s. Apropos why didn't multiple dispatch get picked up by other languages. There'd been a paper which counted what percent of call sites were polymorphic in some large Lisp code base. It wasn't large. This seemed to be widely misunderstood as indicating a lack of importance, overlooking that the organizational impact could still be large. The field failing to maintain a coherent picture of state of play was a problem even back then. Though more simply, multiple dispatch isn't a small thing you can bag on the side. Of the popular languages with run-time type information available, Java was doing simplicity to get hot-swappable programmers, Perl a minimalist OO kludge, and Python "there's only one way to do it" (and it's not multiple dispatch).

Before Julia, other FORTRAN replacement languages had multiple dispatch. Fortress died with Sun. Mathematica continues to putter along, but the 1980s-like closed-source commercial "we'll sue anyone who copies us" model unsurprisingly limits broader impact.

In an alternate timeline, Rich Hickey punted on Closure, and the MIT Julia folks on Julia, and we're still waiting.

tomkwong
The power of multiple dispatch is an eye opener for me personally when I first learned about Julia.

It's hard to get back to OOP once you're used to multiple dispatch. It's so natural and gets rid of the the question of "which class should I write this method in"?

People often talk about speed as an advantage of Julia (which is true), but the real secret advantage compared to any other language I've worked with is more of a network effect: composability

I didn't appreciate the importance of this (or the degree to which this just works as a result of multiple dispatch), but there's a good talk about this from JuliaCon 2019 [1] by Stefan Karpinski

[1] https://www.youtube.com/watch?v=kc9HwsxE1OY

KenoFischer
One aspect that's underappreciated is that composability can actually lead to speed. If it's super easy to compose your algorithmic package with speed-improvement packages (parallelism, hardware accelerators, custom compilers), you're much more likely to be actually able to use them.
Jun 10, 2020 · dTal on JuliaCon 2020 Goes Online
Did you mean "unreasonable effectiveness"? :P

I just watched that talk the other day, and boy is it insightful. Julia has cracked a fairly magical combination of features. I fully expect it to replace Python over the next 10 years.

Direct link to "Unreasonable Effectiveness": https://www.youtube.com/watch?v=kc9HwsxE1OY

Object orientation is not really about making use of structs - many (most?) functional languages also use structs; it's just a grouping of related data.

Object-orientation is about grouping together functionality and data. I.e., an object consists of both a struct, and the functions that act on that struct, known as methods.

In OO, your type additionally refers to the functions called on the data, not just on the data. E.g. you might have a car and a boat and they both only have a position and velocity as data, but the boat has a "sink" method the car doesn't.

There are also debates on what qualifies as "true" object orientation. https://stackoverflow.com/questions/250062/what-is-meant-by-...

For games programming in particular, there is a paradigm known as ECS or Entity Component System. (Depending on your view you might call this a sub-paradigm of object orientation, but I think it's much more accurately described as an alternative.) As the Wikipedia article states:

> An entity only consists of an ID and a container of components. The idea is to have no game methods embedded in the entity.

https://en.wikipedia.org/wiki/Entity_component_system

EDIT: There's also the very interesting paradigm that e.g. Julia uses known as "Multiple Dispatch". This is kind of like defining methods on objects, except that the method is defined on a collection of objects instead of a single one.

E.g., in traditional OO, you might have a vehicle#crash method. And it might take another vehicle as argument, e.g. car.crash(truck). But in multiple dispatch you define a function crash that takes in two vehicles, and then depending on the type of the vehicles given, it changes its behaviour so that crash(car, truck) is different from crash(car, car).

In a sense, the function is not thought of as belonging to either the car or the truck, but as belonging to the pair of them, so conceptually this is different to OO.

I'm not particularly familiar with the paradigm so I'm sure I'm not doing it justice, but you can read further on Wikipedia and in the Julia docs, or the given video:

https://en.wikipedia.org/wiki/Multiple_dispatch

https://docs.julialang.org/en/v1/manual/methods/index.html#M...

https://www.youtube.com/watch?v=kc9HwsxE1OY

Jun 05, 2020 · cbkeller on Julia as a CLI Calculator
Regardless of what is a subset of what, I certainly agree with the OP that OOP feels "anemic" in comparison to the dispatch-based paradigm of Julia.

I can't tell you how many times I've seen disparate packages written by different authors with no explicit effort towards interoperability "just work" together in Julia (e.g., [1]), something I have never seen to a comparable degree in any other language.

[1] https://www.youtube.com/watch?v=kc9HwsxE1OY

It's hard to say limited benefit when the entire Julia ecosystem evolved to so heavily rely on it, to allow for each package to work on it's own level of abstraction: For example, the Julia's standard library implements all of the basic math operators on the CPU, and libraries like Flux then define it's own methods to implement the higher level operators used in ML (such as convolutional layers and activation functions). And then someone writes those same basic math operators but instead of running on CPU they run on GPU, and for that they use a new type (CUArray). The original library knows nothing about CUArrays, it will call the same basic operators as always, but since they have a different type (received from the user) they'll dispatch to the GPU version.

This kind of interaction can grow indefinitely, for example if you use a complex number type/library it will change the basic operators to deal with both real and imaginary parts, and if you use the GPU types within it, then it will do complex math in the GPU (and ML on complex math on GPU..., without any of the libraries being aware of the other). You can see a more detailed explanation on:

https://www.youtube.com/watch?v=kc9HwsxE1OY

This video is my best attempt to explain it (not a rickroll, I swear): https://youtu.be/kc9HwsxE1OY
s1t5
Nice talk, you're a really good speaker. Thanks.
Yes, Julia makes some impressive stunts - supercomputing [0], ml where code is very close to mathematical description [1], code reuse [2] etc.

[0] https://juliacomputing.com/blog/2019/04/12/Supercomputing-ju... [1] https://www.youtube.com/watch?time_continue=188&v=9KBaRS2gy-... [2] https://www.youtube.com/watch?v=kc9HwsxE1OY&t=130s

Nov 26, 2019 · StefanKarpinski on Julia v1.3
> Perhaps Julia's multiple dispatch helps here (?), but why would the above be the case and wouldn't be achievable in other existing languages as is?

Yes, ubiquitous, zero-overhead multiple dispatch is the reason, as explained in depth here:

https://youtu.be/kc9HwsxE1OY

coldtea
Thanks, checking!
The nice thing, is that as a julia programmer get to choose how 'fortran-like' things get. You often start off writing naïve dynamic code that is more like Python or Lisp, and then identify performance bottlenecks and optimize those. This prescription is familiar to Python programmers except the 'optimize this bottleneck' step involves leaving CPython and using Cython or Numba or whatever.

In julia, you optimize your code by just writing slightly uglier julia code, not leaving the language. This has huge implications for

1) People 'looking under the hood'. Instead of having to read C as well as Julia, they only need to read Julia (albeit, more advanced, ugly technical julia code). This tends to greatly lower the bar for a julia user to become a julia developer and actively encourages learning more about the language.

2) Programs 'looking under the hood'. Code modification, injection and re-use is really common in julia and is part of what makes writing it so magical. Part of that is multiple dispatch code: https://www.youtube.com/watch?v=kc9HwsxE1OY but another major thing this enables is prevalent language wide automatic differentiation tools. Julia users tend to take it for granted at this point that they can take just about any function which takes in a number and spits out a number and then take a derivative of that code at compile time, no matter if that function was ever written with auto-diff in mind or if it ever knew about tools like Zygote or Forward diff. This sort of stuff would not be nearly as powerful in a language which did a lot of calling out to other languages to achieve performance.

The entire idea of julia is trying to get as much expressiveness and dynamism as possible while still allowing for C/Fortran levels of speed.

Hello Julia - and it's unreasonably effective multiple dispatch system.

https://www.youtube.com/watch?v=kc9HwsxE1OY

f(x1,x2){} < x1.f(x2){x1} < f(x1,x2){x1,x2}

But modules are useful for organising code and choosing what to bother the compiler with.

Aug 03, 2019 · 7 points, 1 comments · submitted by open-source-ux
ChrisRackauckas
That's a nice example, though I would like it if Stefan had the discussion about templates inside of the slides to make that point really clear since that's where the key difference lies. With templates, you can make static code get generated for the different combinations, but it's still static. And it will only generate the combinations that it knows about. If you compile a shared library and ship this off, and then someone makes a new Pet type and calls that function from your shared library, it won't get a new templated call because the behavior is static and you didn't make this new variant. Multiple dispatch is inherently dynamic and always specializes, and it's a compiler optimization that it generates fast static code in Julia, though that's not necessitated by the feature itself.
Aug 01, 2019 · eigenspace on JuliaCon 2019 Videos
If anyone is looking for video recommendations to get started: here’s a couple that I thought were great and should be interesting and approachable to just about anyone.

Steven G. Johnson’s keynote address. It’s longer format but really interesting, informative, entertaining and well delivered. The main topic is metaprogramming. https://youtu.be/mSgXWpvQEHE

Stefan Karpinski’s talk on the unreasonable effectiveness of multiple dispatch. He discusses the fact that Julia has such dramatic code re-use and sharing between libraries and he discusses some reasons why he thinks multiple dispatch is uniquely able to offer this. https://youtu.be/kc9HwsxE1OY

Jul 29, 2019 · 3 points, 0 comments · submitted by eigenspace
HN Theater is an independent project and is not operated by Y Combinator or any of the video hosting platforms linked to on this site.
~ yaj@
;laksdfhjdhksalkfj more things
yahnd.com ~ Privacy Policy ~
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.