I'm aware it's been discussed in the past and been ruled out for both compile time performance and its semantics, but given how much of a help it seems to be in numerical applications, could someone help me understand the extent of the performance implications if some form of multiple dispatch were introduced? I've searched back as far as Zulip history will let me, sorry if this was a common discussion before!
E.g given restrictions like
plus cannot be generic over the other argument)I would have thought (citation needed) that the penalty will be limited, as long as it doesn't become widespread practice across the community?
Not to belabour the point, but this would also enable default arguments, as well as fallbacks or default implementations (eg if the argument implements only is_lt and is_eq but not is_gt). I can understand why that may not be desirable though.
edit: I do understand also the undesirability of APIs that use inscrutable operator overloading, but I was wondering if there was a narrower version of multiple dispatch that could at least tackle the performance issues.
A message was moved here from #beginners > Type based pattern matching for operator overloading by Jonathan.
I'm not really the guy for this, but I know multiple dispatch requires run time type information (rtti) to be stored alongside the values. There might be a clever way to do that which doesn't involve boxing, like storing the metadata before the value, but it would still have pretty big impact on scalar types. It's why int-s are not objects in Java. They need to be stack allocated, unboxed to achieve reasonable performance. We're aiming to be faster than go and one way we do that is with less boxing. No interface objects, but rather static dispatch. Even closures are stack allocated in Roc.
I would also imagine platforms to have issues with rtti. Either each re-implement dispatching, or exposed functions would be wrapped in another function that would handle the dispatching. But then what are the types that should be accounted for in code-generations? All of them? That's not possibel. You would need to restrict exposed functions. Not a big deal, ppl wouldn't expect it to work across language boundaries, but still.
One of the nice properties of static dispatch as it stands currently in roc is that operators desugar to simple methods i.e. functions. If you implement certain functions, you've implemented the operator. This would no longer be the case if we make operators special. Idk if that's a worthwhile tradeoff, but something to keep in mind.
Someone who knows more about multiple dispatch should correct me though, as this is just my brain-dump.
Oh yeh of course, if it did require rtti it would be a non-starter. But I was wondering only about multiple dispatch where the variant can be determined statically. I.e.
vec.plus(2)
would be determined at compile time as the variant that accepts a where [a.to_i32 : a -> I32] without need for dynamic dispatch. Similarly,
get_screen_pos : Vec2(F32), F32, Vec2(F32) -> Vec2(F32)
get_screen_pos = |world_pos, zoom, offset| world_pos * zoom + offset
In situations where it is not possible to determine which variant from only local context, it would be a compile error, which would both limit compiler work and make it clear as a reader which variant is being used.
If you implement certain functions, you've implemented the operator.
I believe it still would be: you still have your module define is_eq, but it can also define another is_eq for a different set of types.
This would no longer be the case if we make operators special.
The way I see it, operator methods are already special because of their privileged use in syntax. Permitting dispatch to plus(I64), plus(Vec) etc. takes advantage of that terseness.
Anyway I don't want to hammer on about it if it has been ruled out :sweat_smile: Just seemed like there was still a direction available there.
I think it has been discussed in the past and leads to some behaviours that can be confusing.
For example, it often breaks symmetry or requires being able to extend already existing types to implement well.
I definitely see the general argument in general for static dispatch being extended with essentially overloading, which I think is the core ask here
I have heard of complaints/bugs from the first match semantics for example.
Also. I though we had a plan to allow from_numeric or something like that to enable exactly what you are showing. Not sure what happened to those ideas. Also, not sure if they had issues or not
I don't know where in the video it was, but the most recent time I had Chris Lattner on Software Unscripted he basically said Hindley-Milner + overloading destroyed Swift's compile times and he wouldn't do it again
frankly I think higher kinded polymorphism has a higher chance of ever being in Roc than multiple dispatch :sweat_smile:
Ok...I thought there was a way around this without multiple dispatch via where causes, but as I type it out, I think I'm wrong.
Even if it does work, it would definitely require extending builtin types or some weird encode + decode step. So I don't think it would make sense in practice
Richard Feldman said:
I don't know where in the video it was, but the most recent time I had Chris Lattner on Software Unscripted he basically said Hindley-Milner + overloading destroyed Swift's compile times and he wouldn't do it again
Ah, yeh that rings a bell. And fair enough, better to not follow in Swift's footsteps :joy:
Maybe it's a very stupid idea, but could a+b desugar to (a,b).plus()? Then it would do single dispatch on the tuple... Kind of like multiple dispatch, but cheating...
The + binop does de-sugar like that. The desugar section in langref isn't written yet, but there is an example at https://github.com/roc-lang/roc/blob/main/docs/langref/static-dispatch.md#plus
Or maybe I misunderstood the idea.
Looks like it is a.plus(b), this is for 4 + 2:
(e-dispatch-call (method "plus") (constraint-fn-var 139)
(receiver
(e-num (value "4")))
(args
(e-num (value "2"))))
Richard Feldman said:
[…] Hindley-Milner + overloading destroyed Swift's compile times and he wouldn't do it again
Maybe all of you know this, but:
This sounds about right, appearently \lambda-calculus plus overloading is already NP-complete (read: „requires a SAT-solver“):
https://web.cs.ucla.edu/~palsberg/paper/dedicated-to-kozen12.pdf
Although one should point out that „overloading“ here means ad-hoc polymorphism and not parametric (generic) or ranked (inferred dimensionality) polymorphism.
I'm not sure where rocs current capabilities place its type inference/checking at the moment though with regards to complexity (and would like to know).
albx said:
Maybe it's a very stupid idea, but could a+b desugar to (a,b).plus()? Then it would do single dispatch on the tuple... Kind of like multiple dispatch, but cheating...
very close, plus desugars to a.plus(b)
roc does not have (or intend to have, at least atm) rank-n types, nor do we have higher kinded types
Last updated: Jul 23 2026 at 13:15 UTC