based on #ideas > static dispatch - proposal
here's a proposed new operator desugaring table, intended to facilitate arithmetic operator overloading via methods: https://gist.github.com/rtfeldman/caf17cd36ef9945f32ebdf107e87343d
some notes:
plus over add, so that something like Set.add wouldn't accidentally overload an operator. (We use the name Set.insert but add is a common name for that function in other languages, and I can imagine it seeing use in libraries that didn't intend to overload.)!a desugaring to Bool.not(a) instead of a.not() because I haven't heard of a compelling reason to support overloading the unary ! operator.<< and >> operators (which we've had requests to add in the past) desugaring to Num. functions directly so that they aren't overloadable. I'm not aware of a compelling reason for custom numeric types (e.g. vectors, matrices, and complex numbers) to support bit shifting directly, and this one would seem very likely to be used for non-numeric things (e.g. cout << "foo" in C++). That said, in the method-style world there might not even be significant demand for these, since my_num.shl(1) compared to my_num << 1 doesn't seem like a huge difference to me.&& and || desugaring to short-circuit evaluation, which is what most languages do. This is what people most likely expect when coming from other languages, and it's more important to match this expectation now that we have purity inference and what comes after the && or|| might run effects (or not). I also used the qualified True and False from #ideas > custom typesso in this design, you can make a custom type that Just Works with the + operator by exposing a function named plus from the module where that custom type is defined
Richard Feldman said:
Derin Eryilmaz said:
For example, it's common in many languages to have a function like Set.add.
(We happen to call it Set.insert, but Set.add was considered too.) If that's
the best name for your function, is it okay that + now Just Works on your type,
even if that usage doesn't make sense? It's a valid question. Also, the + operator
in arithmetic is commutative, but nothing says a.add(b) has to be
commutative; b doesn't even have to be the same type as a!What about a new operator
++that desugars toappend?that's interesting! I thought originally it didn't seem worth it, but in the context of operator overloading the tradeoffs are different - e.g. having
++would discourage overloading+for that operation
I didn't include this in the gist because it seems like something we could discuss separately on its own. (We don't currently have a ++ operator, so there's nothing to consdier overloading.)
one alternative idea for naming that I considered:
Luke Boswell said:
one idea I like that would side-step the "naming" problem and not accidentally implement overloading is to use the unicode character. Like this;
Bool.'+' : Bool, Bool -> Bool Str.'+' : Str, Str -> Bool
Something along these lines is an option (e.g. Elm calls the "plus" function (+)) but I like the simplicity of teaching it as "these are normal functions and here's what + desugars to"
an upside of names like plus and minus is that they more strongly suggest "this is for math, not DSLs"
like if you really want your Email type to support + then you have to expose a function named Email.plus, which definitely feels out of place
whereas ComplexNum.plus fits naturally
this also shows
&&and||desugaring to short-circuit evaluation, which is what most languages do.
I think we will want to add & and | if those short circuit or are boolean specific. Cause matrix elementwise bitwise and/or are pretty common for example. I assume this will apply to other types as well.
- I intentionally kept
!adesugaring toBool.not(a)instead ofa.not()because I haven't heard of a compelling reason to support overloading the unary!operator.
I think we should definitely allow overloading !, it maps nicely to lots of different things. The boolean complement is a special case of the broader https://en.wikipedia.org/wiki/Involution_(mathematics).
Brendan Hansknecht said:
this also shows
&&and||desugaring to short-circuit evaluation, which is what most languages do.I think we will want to add
&and|if those short circuit or are boolean specific. Cause matrix elementwise bitwise and/or are pretty common for example. I assume this will apply to other types as well.
that seems like it has the same tradeoffs as >> and << then, I guess - really comes down to whether methods for bitwise ops are sufficient in practice
Luke Boswell said:
- I intentionally kept
!adesugaring toBool.not(a)instead ofa.not()because I haven't heard of a compelling reason to support overloading the unary!operator.I think we should definitely allow overloading
!, it maps nicely to lots of different things. The boolean complement is a special case of the broader https://en.wikipedia.org/wiki/Involution_(mathematics).
:thinking: do people use it that way in other languages? I didn't think overloading unary ! was really done, but maybe I'm wrong?
Here's an example from the Plane-based Geometric Algebra stuff I've been looking at recently https://enkimute.github.io/ganja.js/examples/coffeeshop.html#pga2d_points_and_lines
// We define them using the dualisation
// operator (!) to be independent of choice of basis (e12 vs e21)
var point = (x,y)=>!(1e0 + x*1e1 + y*1e2);
that seems like it has the same tradeoffs as
>>and<<then, I guess - really comes down to whether methods for bitwise ops are sufficient in practice
Really depends on what we want to support. In a lot of math heavy uses, they are super nice. Like hash algorithms. Sometimes useful for quick physics sims and other math tricks as well. Definitely helps a lot with readability.... though I guess you would need them on nominal unions to make them useful due to having no way to deal with using scalars with vectors and matrices.
I see a != b desugars to a.not_equals(b). Will there be a way to do default implementation so that you only have to implement equals and not_equals just uses the default? (Could be that I missed it in the dispatch proposal discussion)
I don't think that would be worth the complexity to save implementors one line of code :big_smile:
I seen to recall reading that there were some situations where you could implement not equals more efficiently, so giving people the option to improve performance seems more important
(more efficiently than calling equals and negating the answer)
If a.pass_to(f, b, c) is just desugaring to f(a, b, c), wouldn't it also automatically allow effectful functions making .pass_to! redundant?
At desugaring we don't know if the function is effectful or not. We could produce a warning if the function is suffixed and pass_to isn't, but you could also pass an effectful lambda there and we wouldn't be able to tell until type checking.
I guess we just need to keep meta about how you called it in the Call node so we can check later
(deleted)
Agus Zubiaga said:
If
a.pass_to(f, b, c)is just desugaring tof(a, b, c), wouldn't it also automatically allow effectful functions making.pass_to!redundant?
true, although
Agus Zubiaga said:
At desugaring we don't know if the function is effectful or not. We could produce a warning if the function is suffixed and
pass_toisn't, but you could also pass an effectful lambda there and we wouldn't be able to tell until type checking.
yeah, that's a good point. Mainly it just seems
Agus Zubiaga said:
If
a.pass_to(f, b, c)is just desugaring tof(a, b, c), wouldn't it also automatically allow effectful functions making.pass_to!redundant?At desugaring we don't know if the function is effectful or not. We could produce a warning if the function is suffixed and
pass_toisn't, but you could also pass an effectful lambda there and we wouldn't be able to tell until type checking.
yeah, true - but it just looks weird to me to see .pass_to(f!, a, b, c) and then running .pass_to results in effects happening, because it violates the principle of "if a call does effects, there's a ! on it"
I think it would be pretty straightforward to propagate that info through to type-checking (like "this was called via pass_to without exclamation mark") and give a warning if you used .pass_to when .pass_to! would have been more appropriate
Yeah, that was a morning brain comment. We already have a CalledVia type on can Call nodes that we could use for this.
Glad to see that and and or will shortcircuit now, that definitely seems important with purity inference
yeah we can make that change anytime if anyone wants to work on it
not blocked on anything, and would already be useful now that purity inference is here! :big_smile:
If we’re really concerned about accidentally overloading an operator, would it make sense to be even more explicit in the naming and have all the functions be named _operator as a suffix? So instead of plus, it would be plus_operator for example.
I'm not concerned at all about it if we use the names in that proposal
I think the nicest design is one where we use ordinary names that don't collide, so we can also do things like num.minus(5).abs()
Last updated: Jun 16 2026 at 16:19 UTC