On 14/09/2024 22:48, Jordan LeDoux wrote:

1. Should the next version of this RFC use the `operator` keyword, or should that approach be abandoned for something more familiar? Why do you feel that way?

2. Should the capability to overload comparison operators be provided in the same RFC, or would it be better to separate that into its own RFC? Why do you feel that way?

3. Do you feel there were any glaring design weaknesses in the previous RFC that should be addressed before it is re-proposed?


I think there are two fundamental decisions which inform a lot of the rest of the design:

1. Are we over-riding *operators* or *operations*? That is, is the user saying "this is what happens when you put a + symbol between two Foo objects", or "this is what happens when you add two Foo objects together"? 2. How do we despatch a binary operator to one of its operands? That is, given $a + $b, where $a and $b are objects of different classes, how do we choose which implementation to run?


One extreme is the "operators are just methods with funny names" approach: $a + $b is just sugar for $a->operator+($b); $a can do whatever it likes, but if it doesn't implement the operator, an error happens. There's no need to indicate reversed operands, no implementation on $b is never called.

This is simple to implement, and great for users who want to build concise DSLs; but that degree of freedom is often unpopular.


Towards the other end on question 1, you have defined *operations* with expected semantics, return types, relationships between operators, etc. The previous RFC actually went down this route for comparisons, defining a single "operator <=>" that actually overloaded all the comparison operators at once.

I think if we're going down that route, a name like "__compare" or "interface Comparable { function compare(...) }" makes more sense - you're not actually saying "this is what happens if you type a spaceship", you're saying "here's how to compare two objects".


On question 2, there are a few different possibilities.

Despatch based on type:

a) Binary operators are defined globally on specific type pairs, and the "best" overload chosen from all those currently loaded b) Slightly more restricted: they are defined as static methods, and the best overload chosen from the union of those defined on classes A and B (this is how C# works) c) Operator overloads are only possible between a class and a scalar/non-object, or a class and one of its ancestors; the implementation on the most specific class is used (e.g. if B extends A, B's implementation will be used)

All of these can be written in a way that guarantees consistency ($a + $b will always call the same as $b + $a). Both (a) and (b) would be quite alien to PHP, which doesn't otherwise have multiple despatch, but (c) is quite tempting as a conservative approach.

Despatch by trial and error:

d) Each class can only define one overload for an operator, but can specify which types it accepts; if the definition on type A does not accept instances of B, the definition on type B is attempted e) Operator overloads all accept "mixed", but the definition on A can dynamically return a value which causes the definition on B to be attempted (this is how Python works) f) Instead of returning a special value, allow throwing a special exception; can be combined with option (d) by having the system catch any TypeError g) As in the previous proposal, the implementation on class B is only called if no implementation on class A exists

Each of these can be combined with a special case to always prefer sub-classes; e.g if B extends A, then (new A) + (new B) should call the implementation on B first, even though it's on the RHS. (I spotted this in the Python docs, and it seems very sensible.)


Finally, a very quick note on the OperandPosition enum: I think just a "bool $isReversed" would be fine - the "natural" expansion of "$a+$b" is "$a->operator+($b, false)"; the "fallback" is "$b->operator+($a, true)"


Regards,

--
Rowan Tommins
[IMSoP]

Reply via email to