Stream: ideas

Topic: static dispatch - operator desugaring to methods


view this post on Zulip Richard Feldman (Nov 27 2024 at 02:36):

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

view this post on Zulip Richard Feldman (Nov 27 2024 at 02:41):

some notes:

view this post on Zulip Richard Feldman (Nov 27 2024 at 02:42):

so 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

view this post on Zulip Richard Feldman (Nov 27 2024 at 02:44):

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 to append?

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.)

view this post on Zulip Richard Feldman (Nov 27 2024 at 02:46):

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"

view this post on Zulip Richard Feldman (Nov 27 2024 at 02:47):

an upside of names like plus and minus is that they more strongly suggest "this is for math, not DSLs"

view this post on Zulip Richard Feldman (Nov 27 2024 at 02:47):

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

view this post on Zulip Richard Feldman (Nov 27 2024 at 02:48):

whereas ComplexNum.plus fits naturally

view this post on Zulip Brendan Hansknecht (Nov 27 2024 at 03:40):

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.

view this post on Zulip Luke Boswell (Nov 27 2024 at 03:40):

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).

view this post on Zulip Richard Feldman (Nov 27 2024 at 03:57):

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

view this post on Zulip Richard Feldman (Nov 27 2024 at 03:57):

Luke Boswell said:

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?

view this post on Zulip Luke Boswell (Nov 27 2024 at 04:24):

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

view this post on Zulip Luke Boswell (Nov 27 2024 at 04:25):

// 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);

view this post on Zulip Brendan Hansknecht (Nov 27 2024 at 04:48):

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.

view this post on Zulip Kilian Vounckx (Nov 27 2024 at 10:00):

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)

view this post on Zulip Richard Feldman (Nov 27 2024 at 11:58):

I don't think that would be worth the complexity to save implementors one line of code :big_smile:

view this post on Zulip Richard Feldman (Nov 27 2024 at 12:00):

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

view this post on Zulip Richard Feldman (Nov 27 2024 at 12:00):

(more efficiently than calling equals and negating the answer)

view this post on Zulip Agus Zubiaga (Nov 27 2024 at 14:47):

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?

view this post on Zulip Agus Zubiaga (Nov 27 2024 at 14:48):

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.

view this post on Zulip Agus Zubiaga (Nov 27 2024 at 14:51):

I guess we just need to keep meta about how you called it in the Call node so we can check later

view this post on Zulip Sky Rose (Nov 27 2024 at 15:13):

(deleted)

view this post on Zulip Richard Feldman (Nov 27 2024 at 16:50):

Agus Zubiaga said:

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?

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_to isn'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 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.

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"

view this post on Zulip Richard Feldman (Nov 27 2024 at 16:50):

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

view this post on Zulip Agus Zubiaga (Nov 27 2024 at 17:18):

Yeah, that was a morning brain comment. We already have a CalledVia type on can Call nodes that we could use for this.

view this post on Zulip Isaac Van Doren (Nov 28 2024 at 17:09):

Glad to see that and and or will shortcircuit now, that definitely seems important with purity inference

view this post on Zulip Richard Feldman (Nov 28 2024 at 17:23):

yeah we can make that change anytime if anyone wants to work on it

view this post on Zulip Richard Feldman (Nov 28 2024 at 17:23):

not blocked on anything, and would already be useful now that purity inference is here! :big_smile:

view this post on Zulip Kasper Møller Andersen (Nov 28 2024 at 17:29):

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.

view this post on Zulip Richard Feldman (Nov 28 2024 at 17:40):

I'm not concerned at all about it if we use the names in that proposal

view this post on Zulip Richard Feldman (Nov 28 2024 at 17:42):

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