Hey all,
I contacted Jan a few days ago to ask if they were going to try again for
their RFC, but I wanted to get a quick temperature check on this.
I would like to work on this RFC for 8.2, and after going through previous
discussions on the topic, I feel like the right place to start is to limit
the RFC to only the mathematical operators at first.
Based on previous comments, I was considering working from Jan's previous
implementation with these basic changes:
- The only supported operators in the RFC will be the mathematical
operators: (+, -, /, *, **, %) - The RFC would also provide an interface, (something like
MathObjectInterface), that has all the magic methods in it. - The do_operation would be changed to check for the interface being
implemented instead of the specific method.
All of these operators belong to the same "group" of changes, and (broadly)
any object that is valid to override for one of them should be valid to
override for any of them. NOTE: There are edge cases of this statement
certainly.
This would help address some of the concerns that were expressed about
misuse of things like the + operator to add things to a cache. It would
more explicitly take a position on how these operators should be used in
userland code to keep them more consistent with the behavior of these
operators in normal PHP code.
This would be my first contribution and my first RFC, so I wanted to get
some very broad feedback on this before diving in.
It would suggest that future sets of operators could come with their own
interfaces to also attempt to guarantee an all-or-nothing approach in
userland code, (BitwiseObjectInterface, ComparableObjectInterface,
LogicalObjectInterface, etc.). Though decisions on any of those operators
or their implementations would be left as future scope.
Jordan
Hey all,
I contacted Jan a few days ago to ask if they were going to try again for
their RFC, but I wanted to get a quick temperature check on this.I would like to work on this RFC for 8.2, and after going through previous
discussions on the topic, I feel like the right place to start is to limit
the RFC to only the mathematical operators at first.Based on previous comments, I was considering working from Jan's previous
implementation with these basic changes:
- The only supported operators in the RFC will be the mathematical
operators: (+, -, /, *, **, %)- The RFC would also provide an interface, (something like
MathObjectInterface), that has all the magic methods in it.- The do_operation would be changed to check for the interface being
implemented instead of the specific method.All of these operators belong to the same "group" of changes, and (broadly)
any object that is valid to override for one of them should be valid to
override for any of them. NOTE: There are edge cases of this statement
certainly.This would help address some of the concerns that were expressed about
misuse of things like the + operator to add things to a cache. It would
more explicitly take a position on how these operators should be used in
userland code to keep them more consistent with the behavior of these
operators in normal PHP code.This would be my first contribution and my first RFC, so I wanted to get
some very broad feedback on this before diving in.It would suggest that future sets of operators could come with their own
interfaces to also attempt to guarantee an all-or-nothing approach in
userland code, (BitwiseObjectInterface, ComparableObjectInterface,
LogicalObjectInterface, etc.). Though decisions on any of those operators
or their implementations would be left as future scope.Jordan
If you wanted to cluster them, I would strongly recommend much smaller groupings. IMO, adding two collections to get another collection -- like we add/merge arrays -- is a completely reasonable thing to do, but would only use + and potentially - (for removing items from a collection and returning the smaller collection). But * and / make no sense in that context.
Even if you confine yourself to mathematical concepts, matrix multiplication is a well-established thing that would make sense for math-centric applications to do. But matrix division is... somewhat more fiddly, and has multiple possible definitions. (Note: I've not done matrix math in over 20 years, so I'm basing that statement on some quick googling.)
So I don't think force-grouping the overloads makes sense, in practice. I'm still open to operator overloading in general, but I don't think the clustering is going to help. The weird and complicated type interactions, particularly when it comes to commutablity, are a much larger issue, IMO.
--Larry Garfield
My recommendation is to have a base interface for every operator that can
be overloaded, so that these can be composed together.
AddOverloadInterface { __overloadAdd(): self; }
SubOverloadInterface { __overloadSub(): self; }
MulOverloadInterface { __overloadMul(): self; }
DivOverloadInterface { __overloadDiv(): self; }
ModOverloadInterface { __overloadMod(): self; }
LeftShiftOverloadInterface { __overloadLeftShift(): self; }
RightShiftOverloadInterface { __overloadRightShift(): self; }
XorOverloadInterface { __overloadXor(): self; }
etc.
Then if you were implementing matrix math, you could do this:
MatrixMath implements AddOverloadInterface, SubOverloadInterface,
MulOverloadInterface {
}
This prevents you from having to define methods for operations you don't
support.
It's probably worth exploring whether common combinations are worth
defining for convenience.
On Fri, Aug 6, 2021 at 1:49 PM Larry Garfield larry@garfieldtech.com
wrote:
Hey all,
I contacted Jan a few days ago to ask if they were going to try again for
their RFC, but I wanted to get a quick temperature check on this.I would like to work on this RFC for 8.2, and after going through
previous
discussions on the topic, I feel like the right place to start is to
limit
the RFC to only the mathematical operators at first.Based on previous comments, I was considering working from Jan's previous
implementation with these basic changes:
- The only supported operators in the RFC will be the mathematical
operators: (+, -, /, *, **, %)- The RFC would also provide an interface, (something like
MathObjectInterface), that has all the magic methods in it.- The do_operation would be changed to check for the interface being
implemented instead of the specific method.All of these operators belong to the same "group" of changes, and
(broadly)
any object that is valid to override for one of them should be valid to
override for any of them. NOTE: There are edge cases of this statement
certainly.This would help address some of the concerns that were expressed about
misuse of things like the + operator to add things to a cache. It would
more explicitly take a position on how these operators should be used in
userland code to keep them more consistent with the behavior of these
operators in normal PHP code.This would be my first contribution and my first RFC, so I wanted to get
some very broad feedback on this before diving in.It would suggest that future sets of operators could come with their own
interfaces to also attempt to guarantee an all-or-nothing approach in
userland code, (BitwiseObjectInterface, ComparableObjectInterface,
LogicalObjectInterface, etc.). Though decisions on any of those operators
or their implementations would be left as future scope.Jordan
If you wanted to cluster them, I would strongly recommend much smaller
groupings. IMO, adding two collections to get another collection -- like
we add/merge arrays -- is a completely reasonable thing to do, but would
only use + and potentially - (for removing items from a collection and
returning the smaller collection). But * and / make no sense in that
context.Even if you confine yourself to mathematical concepts, matrix
multiplication is a well-established thing that would make sense for
math-centric applications to do. But matrix division is... somewhat more
fiddly, and has multiple possible definitions. (Note: I've not done matrix
math in over 20 years, so I'm basing that statement on some quick googling.)So I don't think force-grouping the overloads makes sense, in practice.
I'm still open to operator overloading in general, but I don't think the
clustering is going to help. The weird and complicated type interactions,
particularly when it comes to commutablity, are a much larger issue, IMO.--Larry Garfield
--
To unsubscribe, visit: https://www.php.net/unsub.php
My recommendation is to have a base interface for every operator that can
be overloaded, so that these can be composed together.AddOverloadInterface { __overloadAdd(): self; }
SubOverloadInterface { __overloadSub(): self; }
MulOverloadInterface { __overloadMul(): self; }
DivOverloadInterface { __overloadDiv(): self; }
ModOverloadInterface { __overloadMod(): self; }
LeftShiftOverloadInterface { __overloadLeftShift(): self; }
RightShiftOverloadInterface { __overloadRightShift(): self; }
XorOverloadInterface { __overloadXor(): self; }
etc.Then if you were implementing matrix math, you could do this:
MatrixMath implements AddOverloadInterface, SubOverloadInterface,
MulOverloadInterface {}
That would be roughly how Stringable/__toString works now. I'd be OK with that, if it's going to be a language trend.
This prevents you from having to define methods for operations you don't
support.It's probably worth exploring whether common combinations are worth
defining for convenience.
Now that intersection types are a thing, I don't think combo interfaces are worth much here. If anything, it would be just more impetus for type aliases to reduce typing. (Human typing, I mean, not code typing.)
--Larry Garfield
It's probably worth exploring whether common combinations are worth
defining for convenience.
My understanding of the current consensus is "let's not".
That's based from the discussion on whether defining 'number' as 'int
| float' would be worth doing, mostly because adding aliases don't
provide much value, and once you've started adding some, where do you
stop? e.g. if we defined 'number' why not also define 'scalar'?
The value for interfaces is even lower than other types, as it's
already possible in userland to define your own combination types:
interface MatrixMath extends __AddInterface, __AddSubInterface,
__AddMulInterface {}
as interfaces can extend multiple other interfaces at once.
cheers
Dan
Ack
I was considering working from Jan's previous
implementation with these basic changes:
That may not be the best place to start, and that something more
similar to the previous (unvoted) RFC may be better:
https://wiki.php.net/rfc/operator-overloading
In particular, the problems with the approach in
https://wiki.php.net/rfc/userspace_operator_overloading is that having
a function return a special constant when unsupported types are used
is...just not the right approach. The function shouldn't be run at all
if the types aren't supported.
Having to understand a block of code, to figure out what types it
accepts, makes code hard to reason about.
Although the union types RFC had passed when the userspace operator
overloading RFC went to vote, the ability to use union types wasn't
taken advantage of in the RFC.
Listing the types supported as a union type for the parameter like:
class Order implements __AddInterface {
public function __add(Product|Discount $rhs) {...}
}
is easier to reason about, and would throw a type-error consistent
with how unacceptable types would behave for other function calls.
I personally find having the functions be defined as static, with the
first parameter being the left hand side, a bit weird. It makes the
code quite a bit harder to read imo*.
I feel like the right place to start is to limit
the RFC to only the mathematical operators at first.
...something like MathObjectInterface
I agree with Larry and Scott, one method per interface is a better
approach. But defining all the interfaces in one go is probably
better, even if the implementation only supports using some of them.
Doing it like that means that when new operators are implemented as
overloadable in PHP 8.3, people would be able to write library code
that takes advantage of those new operators that works on both 8.2 and
8.3, without users having to define the missing interfaces themselves,
even if their app code isn't able to take advantage of the newly
overloadable operators until they upgrade to 8.3.
cheers
Dan
Ack
- This was mentioned in the RFC: "By passing both operands to the
handler, it can decide between the cases on non-commutative operators
($a / 2 vs. 2 / $a), which would be more difficult when only the
“other” operand (besides $this) is passed.", but I can't see an
example of that, and I don't understand it. Perhaps a better
explanation would be enough.
Though also the behaviour for associativity mentioned in the previous
RFC https://wiki.php.net/rfc/operator-overloading#associativity isn't
mentioned in the 2020 RFC and there may be a can of worms in that.
- The only supported operators in the RFC will be the mathematical
operators: (+, -, /, *, **, %)- The RFC would also provide an interface, (something like
MathObjectInterface), that has all the magic methods in it.- The do_operation would be changed to check for the interface being
implemented instead of the specific method.
Hi Jordan,
In a previous thread [1], I wrote about two fundamental design
approaches to operator overloading:
a) Treating operators as arbitrary symbols, which can be assigned any
operation which makes sense in a particular domain.
b) Treating operators as having a fixed meaning, and allowing custom
types to implement them with that meaning.
Both approaches have their pros and cons, but my general impression is
that approach (b) has more support on this list.
For approach (b), I think your proposal to group operations into
interfaces is a good one - it doesn't prevent someone using the
operators to mean something completely different, but it expresses a
clear opinion.
I think using "+" for a union of two collections is really an example of
approach (a) - it doesn't really mean "add", and it's no less arbitrary
than using "<<" to add an item to a collection (as is conventional in
Ruby, for instance). If we want to go down that route, and allow
individual operators to be overloaded, I think we should look for a
syntax that lets you pick the symbol, rather than giving them names
which only make sense in some contexts.
[1] https://news-web.php.net/php.internals/108347
--
Rowan Tommins
[IMSoP]
a) Treating operators as arbitrary symbols, which can be assigned any
operation which makes sense in a particular domain.
b) Treating operators as having a fixed meaning, and allowing custom
types to implement them with that meaning.
I think this is the core design choice that will affect how the
implementation is approached, and having some good discussion around it
before I got into the implementation was the goal of this thread. :) Jan's
proposal for 8.0 fell more into the a) category with each symbol being
given an independent, unrelated, and unopinionated override. That RFC very
nearly passed, the vote was 38 for and 28 against.
My one hesitation in pushing for a b) type implementation right now (which
I favor slightly personally) is that the basic math operators do have very
different meanings between arithmetic, matrix/vector math, and complex
numbers, all of which are in the same domain of "math". Granted, only
objects which represent a number valid for arithmetic could also be used
with other math functions in PHP (such as the sqrt()
or cos()
functions).
However, they are definitely use cases that are well treaded in userspace
code and libraries.
Complex numbers, for example, couldn't implement a __compare() function at
all, as they don't have any consistent and sensical definition of "greater
than" or "less than". This means that if an object represented a complex
number, the following code would be perhaps unexpected to some:
if (10 < $complex) {
// Never gets here
}
if (10 > $complex) {
// Never gets here
}
if (10 == $complex) {
// Never gets here (!!)
}
$comparison = 10 <=> $complex; // Nonsensical, should throw an exception
So while I tend to lean more towards a b) type implementation myself, even
within that I understood there to be some non-trivial considerations.
"Numbers" in PHP are obviously real numbers, instead of matrices or
complex, so all previous semantics of operators and math functions would
reflect that. To me, an ideal implementation of operator overloading would
be both:
- Flexible about the contextual meaning of a given operator.
- Somewhat opinionated about the semantical meaning of an operator.
This is obviously challenging to accomplish, which is why I'm leaving
myself nearly a whole year for discussion and implementation. I don't want
to do this quickly and end up with something that gets accepted because we
want some form of operator overloading, or something that gets rejected
again despite putting in a great deal of work.
Jordan
On Sat, Aug 7, 2021 at 12:07 PM Rowan Tommins rowan.collins@gmail.com
wrote:
- The only supported operators in the RFC will be the mathematical
operators: (+, -, /, *, **, %)- The RFC would also provide an interface, (something like
MathObjectInterface), that has all the magic methods in it.- The do_operation would be changed to check for the interface being
implemented instead of the specific method.Hi Jordan,
In a previous thread [1], I wrote about two fundamental design
approaches to operator overloading:a) Treating operators as arbitrary symbols, which can be assigned any
operation which makes sense in a particular domain.
b) Treating operators as having a fixed meaning, and allowing custom
types to implement them with that meaning.Both approaches have their pros and cons, but my general impression is
that approach (b) has more support on this list.For approach (b), I think your proposal to group operations into
interfaces is a good one - it doesn't prevent someone using the
operators to mean something completely different, but it expresses a
clear opinion.I think using "+" for a union of two collections is really an example of
approach (a) - it doesn't really mean "add", and it's no less arbitrary
than using "<<" to add an item to a collection (as is conventional in
Ruby, for instance). If we want to go down that route, and allow
individual operators to be overloaded, I think we should look for a
syntax that lets you pick the symbol, rather than giving them names
which only make sense in some contexts.[1] https://news-web.php.net/php.internals/108347
--
Rowan Tommins
[IMSoP]--
To unsubscribe, visit: https://www.php.net/unsub.php
a) Treating operators as arbitrary symbols, which can be assigned any
operation which makes sense in a particular domain.
b) Treating operators as having a fixed meaning, and allowing custom
types to implement them with that meaning.I think this is the core design choice that will affect how the
implementation is approached, and having some good discussion around it
before I got into the implementation was the goal of this thread. :) Jan's
proposal for 8.0 fell more into the a) category with each symbol being
given an independent, unrelated, and unopinionated override. That RFC very
nearly passed, the vote was 38 for and 28 against.My one hesitation in pushing for a b) type implementation right now (which
I favor slightly personally) is that the basic math operators do have very
different meanings between arithmetic, matrix/vector math, and complex
numbers, all of which are in the same domain of "math". Granted, only
objects which represent a number valid for arithmetic could also be used
with other math functions in PHP (such as thesqrt()
orcos()
functions).
However, they are definitely use cases that are well treaded in userspace
code and libraries.Complex numbers, for example, couldn't implement a __compare() function at
all, as they don't have any consistent and sensical definition of "greater
than" or "less than". This means that if an object represented a complex
number, the following code would be perhaps unexpected to some:if (10 < $complex) {
// Never gets here
}if (10 > $complex) {
// Never gets here
}if (10 == $complex) {
// Never gets here (!!)
}$comparison = 10 <=> $complex; // Nonsensical, should throw an exception
So while I tend to lean more towards a b) type implementation myself, even
within that I understood there to be some non-trivial considerations.
"Numbers" in PHP are obviously real numbers, instead of matrices or
complex, so all previous semantics of operators and math functions would
reflect that. To me, an ideal implementation of operator overloading would
be both:
- Flexible about the contextual meaning of a given operator.
- Somewhat opinionated about the semantical meaning of an operator.
This is obviously challenging to accomplish, which is why I'm leaving
myself nearly a whole year for discussion and implementation. I don't want
to do this quickly and end up with something that gets accepted because we
want some form of operator overloading, or something that gets rejected
again despite putting in a great deal of work.Jordan
Side note: Please remember to bottom-post.
I think Rowan's breakdown is a bit too pessimistic and binary. There are definitely different possible ways to interpret operator overloading, but IMO there is a reasonable middle-ground.
At one end is the most restrictive, which would be clustering all "related" overloads together. That would be something like this:
interface Arithmetic {
public function __add($arg);
public function __subtract($arg);
public function __multiply($arg);
public function __delete($arg);
}
The intent of clustering like that would be to "force" developers to use it only on number-like things. However, I believe that has a number of problems.
- What is a number-like thing? How number-ish does it have to be?
As an example here, time units. Adding two hour:minute time tuples together to get a new time (wrapping at the 24 hour mark) is an entirely reasonable thing to do. But multiplication and division on time doesn't make any sense at all. Or, maybe it does but only with ints (2:30 * 3 = 7:30?), kind of, but certainly not on the same type. I'm sure we could come up with an infinite number of cases where one or more arithmetic operations are entirely reasonable and well-defined, but others are not.
- We know from experience that it doesn't work.
PHP already has ArrayAccess, which has four methods. It's extremely common for people to implement ArrayAccess and stub out some of the methods with exceptions because they don't make sense in context. I've seen it a bunch, and I've done it a bunch myself ArrayAccess is, basically, operator overloading for four different operators: [], [$key], isset(), and unset(). But plenty of use cases exist for wanting to do only some of those (eg, a read-only map so stub out unset and offsetSet()), and generally speaking, developers have responded to that conundrum by saying "screw it, Exceptions for everybody!"
If we went with a combined interface, I am 100% certain we would see people implementing Arithmetic and throwing exceptions from __multiply() and __divide().
At the other extreme is arbitrary operator definition a la C++. That would look something vaguely like:
class Foo {
public function __override(+)($arg);
}
That would give the most flexibility to the developer. On the one hand, this appeals to me greatly as within 30 seconds of it passing I would personally release an interface like this:
interface Monad {
public function __override(>>=)(callable $arg): static;
}
And a few more along similar lines. The downside is that 30 seconds after that, 15 other libraries would do the same in subtly incompatible ways, and then both Laravel and Symfony would release their own that are incompatible with each other, and it would just be a total mess because you would have NFI what any given operator is going to do. Then FIG would try to define a few to standardize the madness, would take about 10-12 months to do so, but both Symfony and Laravel would go on using their own instead because they're big enough that they can do that, and we'll have a mess basically forever. That is what my crystal ball tells me would happen.
So while this approach appeals to me personally, I think in the long run it's probably a bad idea. My understanding is that many people consider C++'s adoption of this approach a mistake, although I'm not a C++ developer so cannot speak from first hand experience.
The middle-ground is to give each overridable operator a dedicated named method:
interface Addable {
public function __add($arg);
}
That way, people can opt-in to whatever meaning of "add" they want, but it still means that + always must mean "a method called add()". That provides some guidelines as to what you should do with an operator (if you implement _add() and have it return an object that contains less of something, there's a very strong argument that you're just being stupid and your code is bad), and precludes competing custom operators like >>, >>=, etc. (Much as I would love to make use of them.)
This approach also has precedent in PHP, with, I would argue, far greater success than mega-interfaces. Countable and Traversable are very often implemented together. However, they do not have to be. Sometimes you have something iterable that is uncountable (infinite list, lazy list, etc.), or something countable that it doesn't make sense to foreach() over. So you opt-in to whichever bits make sense. You could also separately opt-in to ArrayAccess, which sometimes also makes sense and sometimes not.
I would argue that the micro-interface approach has a far better success rate in PHP, especially when it comes to "magic" behavior/engine hooks. If we're going to adopt operator overloading, that is the safest middle-ground to take.
(Similarly, there's nothing that forces someone to return an actual count from Countable::count(). It has to be an int, but the language would happy let your return random_int()
if you wanted. But the vast majority of the time people use it responsibly and return an int that makes logical sense in context.)
We also have the advantage now of both union types and intersection types. That means if you want to allow your object to add itself, or some other type, and behave differently, you can easily do so by defining __add(Foo|string $other) and tossing a match() statement into your method body. (Side note: Pattern matching would make that even better.) Anything you don't explicitly allow just type errors for you already.
Conversely, if you want to accept an object that is addable, subtractable, and comparable, you can type it exactly like that:
function foo(Addable&Subtractable&Comparabie $var) {}
So the updated type system makes one-off interfaces a lot easier and more practical to work with than in the past.
To be fair, this approach would not prevent weirdos like me from implementing __add() and using it as a Monadic bind operator or something silly like that. However, I believe experience has shown that a combined Arithmetic interface wouldn't stop me from doing silly things either, given experience with ArrayAccess.
That leaves four remaining questions, which apply in any of the above cases:
- What operators do we build in overloading for? I think there are six to start with: The 4 arithmetic operators, concat, and compare. compare should be essentially an internalized version of the custom sort function passed to
usort()
and friends. The others are reasonably self-explanatory.
An interesting possibility I just realized as I was writing this is using bitwise operator overloading in combination with Enums. Would that be "good enough" for enum sets?
enum FileAccess: int implements Andable, Orable {
case Execute = b1;
case Read = b10;
case Write = b100;
case ReadExecute = b11;
case WriteExecute = b101;
case ReadWrite = b110;
case All = b111;
public function __and(FileAccess $other): FileAccess {
return self::from($this->value & $other->value);
}
public function __or(FileAccess $other): FileAccess {
return self::from($this->value | $other->value);
}
}
I don't know if I like that or not, but it's an interesting thought. I'm not sure if negation makes sense to overload. Once the basic pattern is established we could likely add new operators individually fairly easily.
-
None of these approaches resolves the commutability problem. There is no guarantee that $a + $b === $b + $a, if $a or $b are objects that implement Addable. I suspect that problem is fundamentally intractable, and if we want operator overloading we'll just have to suck it up and accept that we cannot guarantee that is always the case. For some that may be a fatal problem, which is fair. It's not a fatal problem for me, personally.
-
Should the methods in question be dynamic or static? In my mind, the only argument for static is that it makes it more likely that they'll be implemented in an immutable way, viz, you'll return a new instance of the object rather than modifying either $this or $other. However, there is no guarantee of that at all. A static method has just as much access to private variables of its own class as a normal method does, so nothing would prevent a static method from modifying one or both of its operands even if we say not to. That's the same as for a normal method. I think the best we can do in either case is to document "please please don't modify the object in place" and move on. For that reason I would favor a normal method, as a static method just makes things more complicated.
-
What if any type enforcement should the language force? Eg, should __add() be required to return static, or do we leave that up to the implementer, as there are likely use cases we're not thinking of? If the engine can handle it I would favor following the pattern of __invoke(): Let the implementer do whatever it wants for both params and return, but an interface can mandate __invoke() (or __add()) with certain parameter and return types if it wants.
--Larry Garfield
On Sat, Aug 7, 2021 at 3:29 PM Larry Garfield larry@garfieldtech.com
wrote:
Side note: Please remember to bottom-post.
(trimmed message)
--Larry Garfield
--
To unsubscribe, visit: https://www.php.net/unsub.php
Sorry about top-posting. :)
Your arguments for why grouping is undesirable are very persuasive. While I
don't think that having exceptions thrown from stubs is necessarily a bad
thing, I definitely understand how this suggests that the grouping is
unsuccessful at "hinting" the developer on implementation, and also how the
grouping may be too restrictive as a language-level feature. ArrayAccess
really is an interesting comparison.
I did very briefly consider the idea of implementing a single magic
method: __doOperation(string $operator, mixed $lhs, $rhs = null)
After thinking on it briefly, I came to many of the same conclusions: this
would result almost invariably in further fracturing between a "Laravel"
way of doing things and a "Symfony" way of doing things which would be
wholly incompatible and inconsistent. It would also be... complex for IDE's
to support in any meaningful way, especially for custom operators that are
not part of the interpreter. (It also would be far more complex for me to
implement as it would probably require some careful changes to the
compiler.)
So I'll simply disqualify the idea of custom operators right now. I do
understand, like you, why some people might want them and use them, but I
feel like it's too complex and risky of a change, and at its core, not a
change that I want to make, so someone else would need to actually do the
RFC if that was the case.
The 4 arithmetic operators, concat, and compare.
I would argue that both modulo and pow (% and *) are arithmetic operators
that should come with the basic four as well. Meanwhile, I think the concat
operator is both more prone to error and less useful. Currently, if you
try to concat an object, it will be cast to a string, and since
__toString() already exists, this behavior can effectively be controlled
already. The main reason that other operators couldn't easily be solved
with supplementary __to() methods is that for many usecases where you'd
benefit from overloading operators, the scalar types don't adequately
capture the state (which is presumably the reason that the program is
representing them as objects).
Further, as detailed in my earlier reply, I don't think we can get away
with only allowing compare. Complex numbers are a trivial example of why,
but there are many others where only some of the comparison operators make
sense. We could, however, allow compare as a minimal implementation to
support the others. If it is implemented, then the others can be derived,
but if it is not then the other comparison operators should be able to be
implemented individually.
None of these approaches resolves the commutability problem. There is no
guarantee that $a + $b === $b + $a, if $a or $b are objects that implement
Addable.
I don't think this is a problem which can be resolved without some
extremely strict limitations on the operator overloading. (Something like,
objects can only be on both sides if they are the same class.) Even then,
there is little we could do to prevent the user code from doing something
like:
function __add($rhs) {
return (random_int(0, 1) == 1 ? true : 5);
}
As such, I think the idea of operator overloading is fundamentally
incompatible with the concern of ensuring commutability. In fact, many of
the usecases for this feature would depend on them not being commutable.
For matrices, A * B != B * A except under specific values. This is in fact
part of the definition of multiplication under matrices, so I feel that
wanting to ensure commutative behavior actually represents misunderstanding
the feature itself. Extending the native operators to work with matrices
using an internal implementation would also result in non-commutative
behavior, because that's the correct behavior for matrices.
I do not see this as inherently a problem to be solved, but it is a thing
to keep in mind when considering the implementation.
Should the methods in question be dynamic or static? In my mind, the only
argument for static is that it makes it more likely that they'll be
implemented in an immutable way.
I also think that dynamic methods are entirely possible here. While
immutability is an inherent property of using operators in nearly any
context, it's not something that we can guarantee when we leave the
implementation of operator handling up to userspace, and requiring statics
won't do that either. Laravel as an example uses static properties and the
__callStatic() method to provide mutable static calls everywhere in the
entire framework with Facades (something there are very strong opinions
about in userland).
As such I think that requiring static methods is a way to make internals
"feel better" about it without actually providing any real guarantees of
consistency or immutability.
What if any type enforcement should the language force?
Personally I do not think that any type enforcement should be required. I
think it should be possible if the application wants it, but I don't
think it should be required.
Jordan
The downside is that 30 seconds after that, 15 other libraries would do the same in subtly incompatible ways, and then both Laravel and Symfony would release their own that are incompatible with each other, and it would just be a total mess because you would have NFI what any given operator is going to do. Then FIG would try to define a few to standardize the madness, would take about 10-12 months to do so, but both Symfony and Laravel would go on using their own instead because they're big enough that they can do that, and we'll have a mess basically forever. That is what my crystal ball tells me would happen.
So while this approach appeals to me personally, I think in the long run it's probably a bad idea. My understanding is that many people consider C++'s adoption of this approach a mistake, although I'm not a C++ developer so cannot speak from first hand experience.
I strongly echo Larry's concern here.
While I tend to be one who wants more language features, not less, the availability of unconstrained operator overloading can beckon an inexperienced and/or undisciplined developer to add operators for many of the classes they implement, whether or not doing is actually applicable or a good idea.
I am even concerned about constrained operator overloading for the same reason. Here [1][2] are a couple essays to argue against operator overloading.
Ruby has operator overloading, and one of the distasteful aspects I found regarding in programming in Ruby was that everybody's Ruby code you looked at felt like I was written in a slightly different language. I would hate that to become the case with PHP, more than it already is.
I am actually surprised that the last operator overload RFC came close to passing because adding it seems like it would open a Pandora's box and yet other proposals that are less Pandoric seem to be anathema on this list.
That said, I am not unsympathetic to the fact that operator overloading may well have some extremely valid use-cases, just ones that I do not personally seem to need on a day-to-day basis when writing code.
Operator overloading seems to me to be of more value to those who are essentially wanting to write DSLs (domain-specific languages.) If that is indeed the case, and there are some DSL like features I wish we could add to PHP that would generally be non-starters for non-DSL usage.
I wonder if there is a way to limit to only DSL use-cases? I doubt there is, but I had to pose the question in case someone else can envision a way to do so.
So my main questions are:
1.) What are use-cases where operator overloading would really be valuable?
2.) Would it possible that instead of adding operator overloading we could add classes that could be extended where PHP actually defines the operators for those classes?
If we can do #2 we can really work through all the issues with commutativity, associativity, etc. and also implement all and only the operators that make sense for the use-case.
My recommendation is to have a base interface for every operator that can
be overloaded, so that these can be composed together.AddOverloadInterface { __overloadAdd(): self; }
SubOverloadInterface { __overloadSub(): self; }
MulOverloadInterface { __overloadMul(): self; }
DivOverloadInterface { __overloadDiv(): self; }
ModOverloadInterface { __overloadMod(): self; }
LeftShiftOverloadInterface { __overloadLeftShift(): self; }
RightShiftOverloadInterface { __overloadRightShift(): self; }
XorOverloadInterface { __overloadXor(): self; }
etc.That would be roughly how Stringable/__toString works now. I'd be OK with that, if it's going to be a language trend.
If this is how it turns out, I would hope for consistent naming, e.g. Addable vs. AddOverloadInterface, Subtractable vs. SubOverloadInterface, and so on.
It's probably worth exploring whether common combinations are worth
defining for convenience.
My understanding of the current consensus is "let's not".That's based from the discussion on whether defining 'number' as 'int
| float' would be worth doing, mostly because adding aliases don't
provide much value,
While it may (or may not?) be consensus, I argue the counter which is that adding number
to PHP to mean int|float
would provide the value of:
1.) Standardization and
2.) interoperability between libraries that use number
.
This argument assumes there would be a strong consensus that number
should actually mean int|float
and not something else, or not some other name.
For clarity I am not arguing that number means int|float — I'd have to review the debate and see if there was strong consensus — but instead that if there is strong consensus that the term number
does unequivocally mean int|float
then there is a value to adding number
as an alias to PHP, again for 1.) standardization and 2.) interoperability.
and once you've started adding some, where do you
stop? e.g. if we defined 'number' why not also define 'scalar'?
Yes, assuming there is strong consensus on what scalar
should mean.
I would use the same argument as above to support adding scalar
as a standardized union type.
The value for interfaces is even lower than other types, as it's
already possible in userland to define your own combination types:interface MatrixMath extends __AddInterface, __AddSubInterface,
__AddMulInterface {}as interfaces can extend multiple other interfaces at once.
I would argue that the micro-interface approach has a far better success rate in PHP, especially when it comes to "magic" behavior/engine hooks. If we're going to adopt operator overloading, that is the safest middle-ground to take.
I think this it yet another argument for implicitly-implemented interfaces as I mentioned last week[3].
Agreed, micro-interfaces are definitely a better approach, but having to explicitly mention them limits their defacto-standardization and thus adoption. One reason is two libraries are unlikely to both use a 3rd party interface that forces a dependency, and thus they will often create two potentially-conflicting interfaces. So the implementor of their interfaces can often only implement an interface from one or the other library, but not both and users of that interface has to choose which one to accept in their variables and parameters as well as which one to return with their functions and methods.
OTOH, two differently named implicitly-implemented interfaces are compatible if both have the same signature. A function that accepts as a parameter an instance of a class from one library that implicitly implements its own interface can accept an instance of a class from the other library that implicitly implements its other identically-signatured interface. Thus interoperability is improved, defacto-standardization is more likely to occur and serendipity is born, as has been shown to be true in the Go ecosystem.
Implicitly-implemented interfaces give us a better way. The library vendors after the first could write classes that are compatible with the original libraries, even if they are competitors, without having to force an external dependency on a (possibly competitive) library.
Laravel and Symphony would be more likely to see eye-to-eye on at least a few of their interfaces even though their overall philosophies are so drastically different.
Conversely, if you want to accept an object that is addable, subtractable, and comparable, you can type it exactly like that:
function foo(Addable&Subtractable&Comparabie $var) {}
That really begs for userland-definable types, e.g.: Additive[4]
type Additive = Addable&Subtractable&Comparabie
function foo(Additive $var) {}
That's been discussed many times before but AFAIK is not moving forward by anyone. (I would propose, but with so many RFCs that I would like to propose I would not know how to implement.)
Is there some prerequisite we need to achieve before we can pursue that?
To be fair, this approach would not prevent weirdos like me from implementing __add() and using it as a Monadic bind operator or something silly like that. However, I believe experience has shown that a combined Arithmetic interface wouldn't stop me from doing silly things either, given experience with ArrayAccess.
Agreed. That is why (IMO) (almost?) all interfaces added to PHP should either be individual, or an aggregate of other interfaces.
If ArrayAccess where simply the aggregate of these interfaces PHP developers would be in a better position:
interface OffsetExists {...}
interface OffsetGettable {...}
interface OffsetSettable {...}
interface OffsetUnsettable {...}
-Mike
[1] https://cafe.elharo.com/programming/operator-overloading-considered-harmful/
[2] https://www.joelonsoftware.com/2005/05/11/making-wrong-code-look-wrong/
[3] https://externals.io/message/115554#115603
[4] https://english.stackexchange.com/a/170247/1164
On Aug 7, 2021, at 10:28 AM, Larry Garfield larry@garfieldtech.com
wrote:
I strongly echo Larry's concern here.While I tend to be one who wants more language features, not less, the
availability of unconstrained operator overloading can beckon an
inexperienced and/or undisciplined developer to add operators for many of
the classes they implement, whether or not doing is actually applicable or
a good idea.I am even concerned about constrained operator overloading for the same
reason. Here [1][2] are a couple essays to argue against operator
overloading.Ruby has operator overloading, and one of the distasteful aspects I found
regarding in programming in Ruby was that everybody's Ruby code you looked
at felt like I was written in a slightly different language. I would hate
that to become the case with PHP, more than it already is.I am actually surprised that the last operator overload RFC came close to
passing because adding it seems like it would open a Pandora's box and yet
other proposals that are less Pandoric seem to be anathema on this list.That said, I am not unsympathetic to the fact that operator overloading
may well have some extremely valid use-cases, just ones that I do not
personally seem to need on a day-to-day basis when writing code.Operator overloading seems to me to be of more value to those who are
essentially wanting to write DSLs (domain-specific languages.) If that is
indeed the case, and there are some DSL like features I wish we could add
to PHP that would generally be non-starters for non-DSL usage.I wonder if there is a way to limit to only DSL use-cases? I doubt there
is, but I had to pose the question in case someone else can envision a way
to do so.So my main questions are:
1.) What are use-cases where operator overloading would really be
valuable?2.) Would it possible that instead of adding operator overloading we could
add classes that could be extended where PHP actually defines the operators
for those classes?If we can do #2 we can really work through all the issues with
commutativity, associativity, etc. and also implement all and only the
operators that make sense for the use-case.-Mike
[1]
https://cafe.elharo.com/programming/operator-overloading-considered-harmful/
[2]
https://www.joelonsoftware.com/2005/05/11/making-wrong-code-look-wrong/
[3] https://externals.io/message/115554#115603
[4] https://english.stackexchange.com/a/170247/1164--
To unsubscribe, visit: https://www.php.net/unsub.php
Hey Mike,
Off the top of my head here are some of the use cases that I think benefit
greatly from this:
- Complex number objects
- Fractions objects
- Matrix objects (math)
- Vector objects (math)
- Time/Date intervals
- Collections
- Arbitrary precision numbers (which are usually represented as a string in
PHP)
Here are some actual user libraries which would probably use them:
- samsara/fermat (this library is mine as a matter of disclosure)
- brick/math
- markbaker/complex
- markbaker/matrix
- krowinski/bcmath-extended
- malenki/math
- markrogoyski/math-php
- rubix/tensor
- numphp/numphp
- mcordingley/linearalgebra
The objects which represent a mathematical object that cannot be cast to
either an integer or a float without losing their meaning are the ones that
stand to gain the most. If you have an arbitrary precision number that will
overflow both ints and floats, then even providing a method to cast them
first doesn't help (that is, adding a __toInt() and toFloat() method would
not help). Matrices and vectors cannot be converted to a meaningful scalar
in any way that would allow the use of operators. Complex numbers not
only can't be converted to an int or float, but as I've said in a few
places, the correct behavior for the >, <, >=, and <= operators would be to
throw an exception as these types of comparison are mathematically
undefined for ZFC set theory.
As for constraints... well, if I had absolutely no concern for
implementation at all, and was just designing what constraints to put on
the magic methods, they would be:
- void is an unsupported return, and failing to return a value results in
an error. - Variables outside the scope of the method cannot be set. This includes
properties on the object which is defining the magic method, and includes
sets that occur in called functions and methods.
I think that the second restriction there would be quite difficult to
accomplish in an implementation. Perhaps it could be done by creating a
readonly temporary copy of the objects in question and then executing the
magic method on that.
In any case, it is certainly possible that we could instead implement some
magic objects which can be extended that have built-in overloading in
specific ways. I think this is actually worse for the following reasons:
- It would be far less obvious to the programmer that an object would
behave differently with a given operator, because the behavior wouldn't be
documented in the code itself. - It would require many different implementations, some of them very close
to each other, to cover the same set of use cases. - It would likely result in some maintainability concerns as more users
demanded different DSL type implementations be included in core, and the
existing ones would need to be maintained.
As I've mentioned, while I understand that to many programmers
commutativity is a requirement of a good language, it is explicitly
incorrect to require commutativity for math operations. The arithmetic
operators are commutative for the real numbers, but there are many valid
math concepts for which they are not commutative, and I feel like pushing
for commutativity is a mistake. It is an attempt to force a behavior that
we want on code that it may be incorrect for.
Jordan
I started reading this thread with a total bias against it and as I read
through it I can only imagine how painful and annoying it is to maintain a
math library in PHP. Operator Overloading is truly a marvelous piece of
functionality for that domain.
However my biased still exists: it would be awful to have to understand
what +
is doing for every file of every library or even different objects
in the same project.
Ruby has operator overloading, and one of the distasteful aspects I found
regarding in programming in Ruby was that everybody's Ruby code you
looked
at felt like I was written in a slightly different language. I would hate
that to become the case with PHP, more than it already is.
+1
2.) Would it possible that instead of adding operator overloading we
could
add classes that could be extended where PHP actually defines the
operators
for those classes?
+1.
In any case, it is certainly possible that we could instead implement some
magic objects which can be extended that have built-in overloading in
specific ways. I think this is actually worse for the following reasons:
- It would be far less obvious to the programmer that an object would
behave differently with a given operator, because the behavior wouldn't be
documented in the code itself.
For my vision, this point doesn't make sense. After reading my reply if
this still holds true please extend upon it if possible.
- It would likely result in some maintainability concerns as more users
demanded different DSL type implementations be included in core, and the
existing ones would need to be maintained.
True, but I see this as a good thing. The bar to bring something into core
is so high that this is a protective layer against overdoing it.
As I've mentioned, while I understand that to many programmers
commutativity is a requirement of a good language, it is explicitly
incorrect to require commutativity for math operations. The arithmetic
operators are commutative for the real numbers, but there are many valid
math concepts for which they are not commutative, and I feel like pushing
for commutativity is a mistake. It is an attempt to force a behavior that
we want on code that it may be incorrect for.
Totally with you here.
It's been nearly a decade since I part ways with Math, but if I'm not
mistaken we can tackle most (all?) of the operator overloading with 2
classes: Number and NumberCollection (for lack of genetics).
Everything in math can programmatically be represented as these two
abstract classes.
Number:
0
1
27
-30
14 + i
f(x)
7/0 (joking!)
10!
15/8
3.141592...
0.9999999999999...
45°
|-10|
log(n)
NumberCollection
Matrices
Sets
Expression
We have precedence for this: Datetime objects provided by PHP have operator
overloading. However it might be best for PHP to steer away from the
implementation and provide the abstract classes so that userland implements
them. These classes would allow operator overloading and if possible maybe
even a small hint of method overloading?
public function __add(Number $number): Number|NumberCollection
{
}
public function __add(NumberCollection $number): Number|NumberCollection
{
}
I confess I may be oversimplifying it too much, but I thought it was worth
asking. This could still be abused for crazy stuff, but by using
inheritance we make a best effort to delimit the scope into a mathematical
context.
My opinion is that operator overloading is bad for the language and is a
must-have feature for mathematical context. This seem to be the closest to
a common ground I can find.
I started reading this thread with a total bias against it and as I read
through it I can only imagine how painful and annoying it is to maintain a
math library in PHP. Operator Overloading is truly a marvelous piece of
functionality for that domain.However my biased still exists: it would be awful to have to understand
what+
is doing for every file of every library or even different objects
in the same project.
May I ask why? Python does not get much criticism that I can find for its
implementation of operator overloading. Its implementation of operator
overloading is very widely used in the language and is also not seen as a
major source of "suck" for lack of a better term. Python allows virtually
all operators to be overloaded, and takes the unique approach of having an
add() method for when the object is on the left and radd() for when the
object is on the right, with precedence always going to the leftmost object.
There are many languages that provide operator overloading, and I've never
seen it described as a bad feature in any of them. I've seen criticisms of
the particular way it was implemented in a few, but not for the feature
itself. I obviously understand the possible problems that could come from
it. I work in PHP every day at work on a codebase that was originally
written 11 years ago and has been maintained, updated, and added to. We run
it on 7.4 now, and every year we have all the devs in the company spend
time on migrating to the next PHP version. In such a codebase, I'm well
aware of what kind of problems I might have encountered with operator
overloading.
But these don't seem to be major sources of problems in most other
languages.
Python even has multiple default overloads.
prints 12
print(4 * 3)
prints PythonPythonPython
print("Python" * 3)
So while I'm aware of the concerns, I'm unconvinced they are founded in
reality. That is, I think the concerns about maintainability and complexity
may be unfounded in real world scenarios. However it is an extremely
consistent opinion here, and I would like to understand why it is viewed
as bad for the language in a more concrete way if that is possible.
It's been nearly a decade since I part ways with Math, but if I'm not
mistaken we can tackle most (all?) of the operator overloading with 2
classes: Number and NumberCollection (for lack of genetics).
Everything in math can programmatically be represented as these two
abstract classes.
This would very heavily depend on how broad they were. For instance, while
complex numbers and fractions are both numbers, they would not implement
all of the same operator overrides most likely. I think this is a workable
solution, in that it would give the core feature in a way that can be
muddled through to math-domain applications. However, I won't be doing that
in my RFC. Not so much because I think it is an untennable solution for
math (although it would indeed be FAR more complex of a solution from an
RFC standpoint that directly allowing operator overrides). Instead, the
reason that I won't be doing that in my RFC is that I would no longer want
it to be accepted.
If I wrote that in my RFC just to get the overrides that benefited the work
I do, primarily around math, accepted into core, it would virtually
guarantee that actual operator overrides would never come for other types
of applications. Every future RFC on this topic would be riddled with
comments about the existing abstract classes, and how all future
implementations have to be done in a similar way. This would artificially
increase the barrier to acceptance for other domains in a way that I do not
believe improves the language, serves the users of the language, or allows
for flexible development of the language into the future.
So while I acknowledge that it may be a good middle ground (and even one
that I personally would accept for my own implementations and code), I am
unwilling to be the person that locks everyone else out of this language
feature by making future RFCs more constrained and difficult to pass.
I constrained this RFC to math operators not because it's the only domain
that would benefit, but because I think it's the one with the most
straightforward and understandable domain uses for everyone on this list
and represents the "minimum" version of the feature.
Jordan
Off the top of my head here are some of the use cases that I think benefit
greatly from this:
- Complex number objects
- Fractions objects
- Matrix objects (math)
- Vector objects (math)
- Time/Date intervals
- Collections
- Arbitrary precision numbers (which are usually represented as a string in
PHP)
I think that although mathematical libraries feel like a natural
example, they're actually rather a small niche in the world of PHP, so
not a great advert for the feature. Collections are not necessarily a
great example either, since defining "+" to mean "combine these two
collections in some type-specific way" is the kind of broad overloading
which is often controversial.
An example which I think would be applicable to many more PHP users is a
"Money" type, with a value, a currency, and perhaps a precision. It's
also an interesting example where all the mathematical operators can be
implemented, but restricted to certain combinations of types, and even
combinations of values.
A real-life example from a C# project has the following overloads
(syntax tweaked to be more PHP-like):
public static operator - (Money $m1, Money $m2): Money
public static operator + (Money $m1, Money $m2): Money
public static operator / (Money $money, float $divisor): Money //
fraction of a Money value
public static operator / (Money $m1, Money $m2): float // ratio of two
Money values
public static operator * (Money $money, float $multiple): Money // note
[Money * Money] is not defined
public static operator < (Money $m1, Money $m2): bool
public static operator > (Money $m1, Money $m2): bool
public static operator == (Money $m1, Money $m2): bool
public static operator != (Money $m1, Money $m2): bool // defined as
!($m1 == $m2)
All operators which take two Money values throw an exception if they
have different currencies.
The [float * Money] case isn't overloaded here, although it would make
sense; I don't know if that's because it can't be in C#'s overload
system, or just that it is missing from this implementation.
Since even a single operator interface can't guarantee that all inputs
will have a valid output, I remain unconvinced that implementing 8
different interfaces for the above is any better than implementing one
interface and stubbing the parts that have no valid inputs.
Regards,
--
Rowan Tommins
[IMSoP]
Responding to a bunch of people at once here, for simplicity:
The 4 arithmetic operators, concat, and compare.
I would argue that both modulo and pow (% and *) are arithmetic operators
that should come with the basic four as well. Meanwhile, I think the concat
operator is both more prone to error and less useful. Currently, if you
try to concat an object, it will be cast to a string, and since
__toString() already exists, this behavior can effectively be controlled
already. The main reason that other operators couldn't easily be solved
with supplementary __to() methods is that for many usecases where you'd
benefit from overloading operators, the scalar types don't adequately
capture the state (which is presumably the reason that the program is
representing them as objects).
Fair point on concat. I am not sure that mod and pow are logical, except for completeness purposes. Either way, the general point is that we can and should start with a smaller set of operators to prove the concept, then expand it in additional RFCs. (Assuming that the pattern would be effectively the same for any operator once the plumbing is in place.)
Further, as detailed in my earlier reply, I don't think we can get away
with only allowing compare. Complex numbers are a trivial example of why,
but there are many others where only some of the comparison operators make
sense. We could, however, allow compare as a minimal implementation to
support the others. If it is implemented, then the others can be derived,
but if it is not then the other comparison operators should be able to be
implemented individually.
Hrm. Having two different versions of comparable feels highly problematic to me, just from an understandablity point of view. We would have to try and figure out which if any combinations necessarily really do have to come together (eg, is implementing < but not > even possible?). I don't think there's a way to implement one of two mutually exclusive interfaces. I'd hate to push comparables to their own RFC as that's also been its own ball of controversy in the past, but...
As such, I think the idea of operator overloading is fundamentally
incompatible with the concern of ensuring commutability. In fact, many of
the usecases for this feature would depend on them not being commutable.
For matrices, A * B != B * A except under specific values. This is in fact
part of the definition of multiplication under matrices, so I feel that
wanting to ensure commutative behavior actually represents misunderstanding
the feature itself. Extending the native operators to work with matrices
using an internal implementation would also result in non-commutative
behavior, because that's the correct behavior for matrices.
A good point. I would urge you to include the Matrix example in the RFC to head off any complaints about commutability.
As such I think that requiring static methods is a way to make internals
"feel better" about it without actually providing any real guarantees of
consistency or immutability.
I concur.
What if any type enforcement should the language force?
Personally I do not think that any type enforcement should be required. I
think it should be possible if the application wants it, but I don't
think it should be required.
So, that's essentially the __invoke() approach I suggested. I'm good with that.
In any case, it is certainly possible that we could instead implement some
magic objects which can be extended that have built-in overloading in
specific ways. I think this is actually worse for the following reasons:
An abstract base class-based implementation is an instant-no from me, full stop. Let's not and say we didn't.
So while I'm aware of the concerns, I'm unconvinced they are founded in
reality. That is, I think the concerns about maintainability and complexity
may be unfounded in real world scenarios. However it is an extremely
consistent opinion here, and I would like to understand why it is viewed
as bad for the language in a more concrete way if that is possible.
I can't speak for Ruby, but I suspect at least part of it is is C++. I have heard plenty of complaints about C++'s overloading implementation, which allows all kinds of arbitrary weridness. That's why I was arguing previously for a more named approach, as that provides a more Python-esque suggestion of how to use a given overload properly.
Adopting Python's r*OP naming to handle rhs overloads is interesting, but I'm not sure I like it. It feels like it may just be begging for more complexity and weird behavior. But I'm open to further investigation of it.
Since even a single operator interface can't guarantee that all inputs
will have a valid output, I remain unconvinced that implementing 8
different interfaces for the above is any better than implementing one
interface and stubbing the parts that have no valid inputs.
-
Implementing an interface and stubbing out some methods is a lie. It's a lie the engine won't stop you from telling, but it's still a lie. If you only support addition and division, for instance (add to a collection and split a collection into several), then having a bunch of comparison methods that your object says you support but the program dies as soon as you use it is a lie, and a landmine waiting to happen.
-
As Jordan demonstrated, there are many even formally defined situations where some of the operators are explicitly meaningless. It would mean in those cases you cannot use this feature without lying, and setting landmines for other developers.
-
If you really do want to use all of those methods, yes, it would mean 8 interfaces. However, there's 2 easy solutions to that: A) We already know that type aliases would be useful. B) We could make the interface implicit the way Stringable is. That way you can just implement the method and move on, but people could still type against Addable or Divisible or whatever.
--Larry Garfield
- Implementing an interface and stubbing out some methods is a lie. It's a lie the engine won't stop you from telling, but it's still a lie. If you only support addition and division, for instance (add to a collection and split a collection into several), then having a bunch of comparison methods that your object says you support but the program dies as soon as you use it is a lie, and a landmine waiting to happen.
I find it odd, given your other comments, that you keep coming back to
collection examples, which seem to me very much in the realm of
re-defining the symbols to mean something different in a new domain. If
"+" can mean "union" and "/" mean "partition", why not also let "<<"
mean "append"?
If we're saying we don't want "arbitrary weirdness", then discouraging
"add" and "divide" on a collection feels like a feature, not a bug.
- As Jordan demonstrated, there are many even formally defined situations where some of the operators are explicitly meaningless.
Yes, and as I demonstrated, there are situations where some of the
operators are only meaningful for a subset of operands. Saying that a
Money object is "Multipliable" would also be "a lie", since multiplying
two Money objects together is a logic error, even though multiplying
Money by a unit-less number is very useful.
Regards,
--
Rowan Tommins
[IMSoP]
On Sun, Aug 8, 2021 at 9:52 AM Rowan Tommins rowan.collins@gmail.com
wrote:
Yes, and as I demonstrated, there are situations where some of the
operators are only meaningful for a subset of operands. Saying that a
Money object is "Multipliable" would also be "a lie", since multiplying
two Money objects together is a logic error, even though multiplying
Money by a unit-less number is very useful.
This is a good point, and your example of currency was a great example of a
whole class of other things that would benefit: unit based values.
I've started to work on the RFC document to collect some of the information
we've discussed in this thread. I do not have the ability to edit
wiki.php.net, so I cannot put the RFC on there at the moment. In lieu of
that, I've created a placeholder markdown document on github for the RFC
page:
https://github.com/JordanRL/operator-overloading-rfc/
This is not where I will work on the implementation (which will obviously
be on a fork of php-src). I've outlined my rough idea of the implementation
I'm leaning towards in this doc, but have left out any discussion about
interfaces for now, as I feel that's something that could use further
discussion. Any feedback would be appreciated at this early stage.
Jordan
Off the top of my head here are some of the use cases that I think benefit greatly from this:
- Complex number objects
- Fractions objects
- Matrix objects (math)
- Vector objects (math)
- Time/Date intervals
- Collections
- Arbitrary precision numbers (which are usually represented as a string in PHP)
Thank you for going to the effort to list those out. I'd like to categorize them, if I may?
General Purpose
- Collections
I'm not sure if you there is something about Collections related to Math that makes them applicable for operator overloading but otherwise I would be question whether there would be a strong consensus around what operators a collection would implement and how those operators would behave.
If you think there would be a strong consensus regarding collections and operators maybe you could elaborate on which operators would apply and what each would do?
Date/Time
- Time/Date intervals
This seems to me to be a great use-case. Ironically we already have the DateInterval class as well as methods that operate on DateTime objects that add and subtract DateIntervals as well as proving the different between two dates.
Because we already have the classes, this seems to be a perfect place to start with an RFC to introduce operators to PHP for DateTime and DateInterval objects.
More on this in a bit.
Math
- Complex number objects
- Fractions objects
- Matrix objects (math)
- Vector objects (math)
- Arbitrary precision numbers (which are usually represented as a string in PHP)
And these all seem like strong candidates for classes that could use operators.
But mathematical concepts are pretty well set in stone; are there really this many different ways to implement them (rhetorical question)?:
Here are some actual user libraries which would probably use them:
- samsara/fermat (this library is mine as a matter of disclosure)
- brick/math
- markbaker/complex
- markbaker/matrix
- krowinski/bcmath-extended
- malenki/math
- markrogoyski/math-php
- rubix/tensor
- numphp/numphp
- mcordingley/linear-algebra
Looking at the tests for just complex numbers in just three (3) of these I see distinctly different choices, choices which appear to be arbitrarily made by each developer:
- https://github.com/SamsaraLabs/FermatComplexNumbers/blob/master/tests/Samsara/Fermat/Values/ImmutableComplexNumberTest.php
- https://github.com/MarkBaker/PHPComplex/blob/3.0/unitTests/classes/src/ComplexOperationTest.php
- https://github.com/malenkiki/math/blob/master/tests/Number/ComplexTest.php
I really can't critique the math aspects nor fully grok your use-cases, but do I understand the benefit of standardization, and it seems like PHP would gain greatly for use in the math domain by introducing classes written in C and baked into core for each of your math use-cases.
And the same with the Money classes that Rowan proposed.
But I know you objected to that approach....more on that at the end.
As for constraints... well, if I had absolutely no concern for implementation at all, and was just designing what constraints to put on the magic methods, they would be:
- void is an unsupported return, and failing to return a value results in an error.
- Variables outside the scope of the method cannot be set. This includes properties on the object which is defining the magic method, and includes sets that occur in called functions and methods.
When I mentioned "constrained" I was referring to Rowan's distinction:
In a previous thread [1], I wrote about two fundamental design approaches to operator overloading:
a) Treating operators as arbitrary symbols, which can be assigned any operation which makes sense in a particular domain.
b) Treating operators as having a fixed meaning, and allowing custom types to implement them with that meaning.
Where a) would be "unconstrained" and b) would be "constrained." I think you assumed I was referring to something else.
In any case, it is certainly possible that we could instead implement some magic objects which can be extended that have built-in overloading in specific ways. I think this is actually worse for the following reasons:
- It would be far less obvious to the programmer that an object would behave differently with a given operator, because the behavior wouldn't be documented in the code itself.
- It would require many different implementations, some of them very close to each other, to cover the same set of use cases.
- It would likely result in some maintainability concerns as more users demanded different DSL type implementations be included in core, and the existing ones would need to be maintained.
I am going to challenge your justifications:
- When you speak of "not documented in the code" do you mean the implementation would not be in PHP code? Okay, yet none of these functions are documented in PHP code and they are all available in PHP:
https://www.php.net/manual/en/ref.math.php
AFAIK few people who do not struggle with programming or math have any problem with these functions, because they are all documented.
- Yes, it would require many different implementations, one for each use-case. Unique logic has to be implemented. Somebody has to do it. Why is that a problem?
I am not sure what you mean by being "very close to each other to cover the same set of use-cases" unless you are talking about many different arbitrarily different userland implementations, which I doubt since that would be a reason not do add operator overloading.
- Regarding maintainability concerns. Code generally doesn't rot unless the requirements change. Are requirements going to change? If yes, then its not a strong use-case for operator overloading, in core or userland. We should guard against operators used for convenience that are leaky abstractions.
BTW nobody can "demand" new features in PHP and be effective. But if they propose them in an RFC it helps ensure these new features are fully fleshed out, to the best of the list's collective ability.
As I've mentioned, while I understand that to many programmers commutativity is a requirement of a good language, it is explicitly incorrect to require commutativity for math operations.
I only mentioned commutativity because others mentioned it. I personally don't have the math chops so I defer to you here.
However my biased still exists: it would be awful to have to understand
what+
is doing for every file of every library or even different objects
in the same project.May I ask why? Python does not get much criticism that I can find for its
implementation of operator overloading.
...
There are many languages that provide operator overloading, and I've never
seen it described as a bad feature in any of them.
While it is hard to objectively quantify how much is "much" this gentlemen — a programming teacher — had some choice words to say about operator overloading specifically related to Python:
https://medium.com/@rwxrob/operator-overloading-is-evil-8052a8ae6c3a
A pull quote from his blog post:
"You wanna know what is not simple? Operator overloading. It has no place in any language. It’s an aberration to be shunned."
He feels even more strongly about the perils of userland operator overloading than I do!
So while I'm aware of the concerns, I'm unconvinced they are founded in
reality. That is, I think the concerns about maintainability and complexity
may be unfounded in real world scenarios. However it is an extremely
consistent opinion here, and I would like to understand why it is viewed
as bad for the language in a more concrete way if that is possible.
Similarly I am unconvinced that adding operator overloading who not end up making PHP programs brittle and that the community would come to loath and wish we could turn back time to remove.
Using operators for most classes would be a leaky abstraction. Developers under the gun to deliver projects for their employers or client are rarely going to take the in-depth time required to fully understand the abstraction and all the ramifications of adding operators, they are just going to do it because "it will seem like a good idea at the time." And that will result in code using operators where the concept of add, subtract, multiply, divide etc will be constantly shifting as a project evolves. Just like what a method means is constantly shifting.
A while back someone proposed __ToArray and Rowan (among others) fought back hard saying that the problem is that exporting an object to array could mean different things to different people and there is often a need to export to multiple different array format, and thus the use-case should always be implemented as a named function. A first I was annoyed that the list was pushing back because I wanted to use __ToArray, but the more I thought about it the more I realized that Rowan and others were right.
Overloaded operators are no different than a __ToArray method. What does __add() really mean for most classes?
Most use-cases are not clear and compelling enough to elevate a named function call to an operator. And for the relatively few use-cases that are clear and compelling it would make sense for the use-case to be fully fleshed out and then an object created in C and added to PHP, just like DateTime and DateInterval were created.
If I wrote that in my RFC just to get the overrides that benefited the work
I do, primarily around math, accepted into core, it would virtually
guarantee that actual operator overrides would never come for other types
of applications. Every future RFC on this topic would be riddled with
comments about the existing abstract classes, and how all future
implementations have to be done in a similar way. This would artificially
increase the barrier to acceptance for other domains in a way that I do not
believe improves the language, serves the users of the language, or allows
for flexible development of the language into the future.So while I acknowledge that it may be a good middle ground (and even one
that I personally would accept for my own implementations and code), I am
unwilling to be the person that locks everyone else out of this language
feature by making future RFCs more constrained and difficult to pass.I constrained this RFC to math operators not because it's the only domain
that would benefit, but because I think it's the one with the most
straightforward and understandable domain uses for everyone on this list
and represents the "minimum" version of the feature.
I appreciate that you have anxiety that if you don't get it all now, you never will. I understand that, I feel the same way about the things I'd like to see added to PHP.
But I have also realized that pushing through a feature too quickly can have far reaching consequences and a damaging outcome. Better to take small steps and allow time for the features to mature than to dive in and make a mistake and have to live with it for the rest of PHP's useful life.
Given some people feel operator overloading is problematic in other languages and some languages like Go explicitly chose not to add it it would behoove us to tread very conservatively instead of just adding it before we have any experience with operator overloading in PHP. Right now we don't know what we don't yet know.
If we were to — for example — start by adding operators to DateTime and DateInterval it would give us real-world experience with operators and objects before we fully commit to adding operator overloading for all classes.
We could then take our learning for DateTime and DateInterval and add another class, maybe ComplexNumber.
Heck, we might even recognize value in creating an imaginary number literal to support it; that type of domain-specific value won't happen if we just focus on generic operator overloading.
Once we have ComplexNumber down, maybe we tackle a few more Math classes, and many we even tackle Money.
After a while, we'd have numerous classes in PHP supporting operators. At that people we could have the confidence needed to decide it and how to implement general-purpose operator overloading.
But one thing is certain. While we can go from special one-off classes that support operator overloading to later adding general-purpose operator overloading, we cannot go the opposite direction. That makes the idea of first testing out operator overloading on specific use-cases a much less risky proposition that just adding general purpose operator overloading from the start.
-Mike
- When you speak of "not documented in the code" do you mean the
implementation would not be in PHP code? Okay, yet none of these functions
are documented in PHP code and they are all available in PHP:https://www.php.net/manual/en/ref.math.php
AFAIK few people who do not struggle with programming or math have any
problem with these functions, because they are all documented.
Yes, that is precisely what I mean. Operator overloading is a complex
feature, I have never pretended otherwise. Having the behavior documented
in the code base you are working on, in the code itself, is FAR more
useful. My primary example of that is this:
You claim that this would be documented on php.net and this would be
sufficient. Yet the DateTime class has had operator overloads for
comparison operators since 5.2 and there is still zero mention on php.net
(that I am aware of or can find) of this. Not even a mention that it
exists, let alone what the override behavior is. Not even in the god-awful
comments on the manual.
Simply put, as someone who has spent my entire career working primarily in
PHP and been working in it since PHP 4, it has one of the very best free
and online manuals for any language I've worked in, and I would still 10
times out of 10 prefer to be able to see the implementation in my IDE than
have to look it up on php.net.
- Yes, it would require many different implementations, one for each
use-case. Unique logic has to be implemented. Somebody has to do it. Why is
that a problem?I am not sure what you mean by being "very close to each other to cover
the same set of use-cases" unless you are talking about many different
arbitrarily different userland implementations, which I doubt since that
would be a reason not do add operator overloading.
What I mean is that there would be multiple "number like" abstract classes
that would be 80% to 90% similar. That is, if it actually correctly
represented the core math ideas. This method however would utterly fail for
unit based value overloads, of which Rowan's money example is a specific
case.
- Regarding maintainability concerns. Code generally doesn't rot unless
the requirements change. Are requirements going to change? If yes, then its
not a strong use-case for operator overloading, in core or userland. We
should guard against operators used for convenience that are leaky
abstractions.BTW nobody can "demand" new features in PHP and be effective. But if they
propose them in an RFC it helps ensure these new features are fully fleshed
out, to the best of the list's collective ability.
Yes, I'm aware. :) The process that PHP employs with internals to maintain
the language is one of its greatest strengths in my personal opinion. I
shouldn't be able to simply open a PR and have the maintainer be the only
one that looks at it. My RFC, whether it is accepted or not, will be better
because of this process. It has already been improved by this thread.
However, creating abstract classes simply for custom operator overloads
would signal to users that this is something internals is willing to do.
Despite the fact that I am confident the process would continue to work, I
would very much anticipate multiple and continuous threads opened by users
wondering when "their" operator overloads are going to be included. While
this obviously doesn't force anything, it absolutely is a distraction.
While it is hard to objectively quantify how much is "much" this gentlemen
— a programming teacher — had some choice words to say about operator
overloading specifically related to Python:https://medium.com/@rwxrob/operator-overloading-is-evil-8052a8ae6c3a
A pull quote from his blog post:
"You wanna know what is not simple? Operator overloading. It has no place
in any language. It’s an aberration to be shunned."He feels even more strongly about the perils of userland operator
overloading than I do!
:/
I was hoping for something more concrete than what he said in this article,
especially when the one and only comment on the article succinctly
expresses why it's incorrect. Python's major "selling point" over other
languages, especially for its initial adoption and wide use in teaching, is
its power in scientific computing, and those libraries 100% depend on
Python's operator overloading.
A while back someone proposed __ToArray and Rowan (among others) fought
back hard saying that the problem is that exporting an object to array
could mean different things to different people and there is often a need
to export to multiple different array format, and thus the use-case should
always be implemented as a named function. A first I was annoyed that the
list was pushing back because I wanted to use __ToArray, but the more I
thought about it the more I realized that Rowan and others were right.Overloaded operators are no different than a __ToArray method. What does
__add() really mean for most classes?
Respectfully I disagree. There can be different meanings for __add() with
different operands, just like 2 + 3
is different than 2.1 + 3.1
. But an
operator heavily implies the idea that there is a single and deterministic
result for a given set of input values and input types. I do not believe
that these are at all similar to _toArray(). The fact that internals cannot
anticipate what they are because the domains and context are varied is not
the same as the results being non-deterministic or usage dependent. They
are not arbitrary in the same way that array structure is.
I appreciate that you have anxiety that if you don't get it all now, you
never will. I understand that, I feel the same way about the things I'd
like to see added to PHP.
That's not exactly what my concern is, though I can also understand that
concern. My concern is that I think I can properly advocate for and explain
the use cases and implementations that would benefit my own work, but I do
not know that others could be able to do so.
But I have also realized that pushing through a feature too quickly can
have far reaching consequences and a damaging outcome. Better to take
small steps and allow time for the features to mature than to dive in and
make a mistake and have to live with it for the rest of PHP's useful life.
This is why I'm targeting this for 8.2 and started this conversation nearly
a full year before the feature freeze for 8.2. I am not going to come
through with the RFC vote in a few weeks, this is going to be slow,
deliberate, and inclusive, because that is the only sensible way that a
change as complex and impactful as what I'm proposing could be done
correctly.
If we were to — for example — start by adding operators to DateTime and
DateInterval it would give us real-world experience with operators and
objects before we fully commit to adding operator overloading for all
classes.We could then take our learning for DateTime and DateInterval and add
another class, maybe ComplexNumber.
Heck, we might even recognize value in creating an imaginary number
literal to support it; that type of domain-specific value won't happen if
we just focus on generic operator overloading.Once we have ComplexNumber down, maybe we tackle a few more Math classes,
and many we even tackle Money.
This is interesting, since we do have operator overloads for DateTime
already and have for quite a long while. This is covered briefly in the
draft RFC I linked in my last message.
An imaginary number literal couldn't really be supported without either a
fully-featured complex number library in core (not just an abstract stub
with operator overloading), or errors/exceptions on multiple operator
combinations. For instance, int + imaginary
would require some
representation of complex number as a result type. This is, to my
understanding, why this is usually left to userland implementations in most
languages, particularly since complex numbers are advantageous to represent
as int + imaginary
in some circumstances and (r, theta)
in other
circumstances.
But one thing is certain. While we can go from special one-off classes
that support operator overloading to later adding general-purpose operator
overloading, we cannot go the opposite direction. That makes the idea of
first testing out operator overloading on specific use-cases a much less
risky proposition that just adding general purpose operator overloading
from the start.
This is a point that I obviously can't casually dismiss, because it's
simply a fact. Once user defined operator overloading exists, it can't
reasonably be undone, however it can always be added on top of abstract
classes in the future. That being said, this is not something that
internals hasn't considered before and there already are custom operator
overloads in C implementations in PHP. There are also extensions that cover
more domain specific C implementations, such as ext-decimal and ext-gmp.
The first version of this feature that I'm aware of was worked on for 7.1.
So while your point is valid and powerful, my contention isn't that you're
wrong, but rather that now is later for this feature. Internals has
looked at this feature before, nearly accepted it into core for 8.0, and
provided a great deal of feedback and discussion on its benefits and
problems. Multiple examples of it in PHP exist in C implementations.
Arrays treat +
as a union operator. Strings and arrays both work with
<=>
and execute some fairly complicated (and opinionated) logic to
determine sort order of those types. DateTime already has overloads for
comparison operators and has for a long time. Both ext-decimal and ext-gmp
have implemented domain-specific operator overloads. In fact, the operator
overloading for GMP done by Nikita for 5.6 covered much of the same
arguments (though it obviously didn't include userland operator
overloading).
I am not dismissing your point here, but I'd like to respectfully suggest
that we now have the information to address the topic, instead of needing
further use cases to consider.
Finally, as I mentioned I won't be entertaining that format for this RFC.
While I have said that I understand the argument, I would suggest that
those who feel it is a better alternative work on it as an RFC if they
believe it is a better route. This RFC may take me the entire year and
still get rejected, and I understand that. I would simply not be a good
advocate for such an approach, because the very first objection is going to
be "why is this use case given preferential treatment?" and I will be
unable to answer because I would raise the same objection myself.
Jordan
You claim that this would be documented on php.net and this would be
sufficient. Yet the DateTime class has had operator overloads for
comparison operators since 5.2 and there is still zero mention on
php.net
(that I am aware of or can find) of this. Not even a mention that it
exists, let alone what the override behavior is. Not even in the god-awful
comments on the manual.
For future reference: https://www.php.net/manual/en/datetime.diff.php
On Mon, 9 Aug 2021 at 18:48, Jordan LeDoux jordan.ledoux@gmail.com
wrote:You claim that this would be documented on php.net and this would be
sufficient. Yet the DateTime class has had operator overloads for
comparison operators since 5.2 and there is still zero mention on
php.net
(that I am aware of or can find) of this. Not even a mention that it
exists, let alone what the override behavior is. Not even in the
god-awful
comments on the manual.For future reference: https://www.php.net/manual/en/datetime.diff.php
Thank you. I honestly did look for it quite extensively.
Off the top of my head here are some of the use cases that I think benefit
greatly from this:
- Complex number objects
- Fractions objects
- Matrix objects (math)
- Vector objects (math)
- Time/Date intervals
- Collections
- Arbitrary precision numbers (which are usually represented as a string in
PHP)
Hi Jordan,
I would like to point out another interesting use case that I have seen
in the Python world: Query builders. In that use case, operator
overloads are used to create database filtering expressions for example,
allowing one to write:
db.products.filter(db.products.price < 100)
Here db.products.price is an object representing a database column
implementing the lt method, overloading the < operator. This method
returns an object representing a database filtering expression, which
gets passed to the filter() method.
So basically, this would allow for writing expressions in plain PHP and
have them translated into other languages under the hood.
The use case does require that operator overloads are allowed to return
any kind of value they wish.
Regards,
Dik Takken
As an example here, time units. Adding two hour:minute time tuples together to get a new time (wrapping at the 24 hour mark) is an entirely reasonable thing to do. But multiplication and division on time doesn't make any sense at all. Or, maybe it does but only with ints (2:30 * 3 = 7:30?), kind of, but certainly not on the same type.
This touches on another important question -- how do we plan to represent:
-
An operator which is only applicable when an operator is used on two dissimilar types (e.g. TimeTuple x float)?
-
An operator which can be applied to an object, but with a primitive or otherwise non-extensible type on the left hand side of the operator (e.g. float x TimeTuple)?
-
An operator which can be used with multiple types, but whose meaning and/or return value can change based on what types are used (e.g. Matrix x Matrix vs. float x Matrix)?
As an example here, time units. Adding two hour:minute time tuples
together to get a new time (wrapping at the 24 hour mark) is an entirely
reasonable thing to do. But multiplication and division on time doesn't
make any sense at all. Or, maybe it does but only with ints (2:30 * 3 =
7:30?), kind of, but certainly not on the same type.This touches on another important question -- how do we plan to represent:
An operator which is only applicable when an operator is used on two
dissimilar types (e.g. TimeTuple x float)?An operator which can be applied to an object, but with a primitive or
otherwise non-extensible type on the left hand side of the operator (e.g.
float x TimeTuple)?An operator which can be used with multiple types, but whose meaning
and/or return value can change based on what types are used (e.g. Matrix x
Matrix vs. float x Matrix)?
--To unsubscribe, visit: https://www.php.net/unsub.php
The short answer is that all three of these concerns must be left up to
the userland implementations. Our job, realistically, should be to provide
a sane and consistent way for their implementations to accomplish this. But
truly, for most of these conditions the interpreter can't make enough
assumptions about the purpose to make any meaningful contribution on
questions like these.
Because of this, as far as actual implementation goes, I was gravitating
towards one of two possible formats:
function __op($rhs) {
} // Binary operators
function __op() {
} // Unary operators
In this implementation, an object could only overload the operator if it
was on the left hand side. There are some advantages to this. It simplifies
the cases within the do_operation function that would need to be considered
for instance.
function __op($pos, $lhs, $rhs) {
} // Binary operators
function __op() {
} // Unary operators
In this implementation, the $pos argument would be 0 if the called object
was on the left, and 1 if the called object was on the right. The right
hand side would obviously not be passed for unary operations (though there
aren't any unary operations that I plan to implement in this RFC). The
major advantage of this implementation is that the code which is most aware
of the context and meaning could fully handle the situation, regardless of
whether the object was on the left or right. This would allow us to provide
something that is more commutative, even if full commutativity isn't
desirable for the reasons I've outlined earlier.
Jordan
Le 06/08/2021 à 18:34, Jordan LeDoux a écrit :
Hey all,
...
Hello,
I'm sorry I couldn't answer the last few weeks I was on vacation.
I just wanted to say, I'm opposed to user-land operator overloading for
a few reasons:
- it may produce shorter, simpler code, but it hides the real logic
behind, - not all mathematical arithmetic operators have a semantic meaning
for let's say, collections: it's not evident to me that "/" means
partition (even thought the "+" for union is trivial), - they are hundreds different operations on collections which have a
common taxonomy in many languages, there will never be enough
operators for all, we will end up with Frankenstein API's where
there will be operators for some operations, and methods for others, - methods in plain english (or whatever is your language) are always
more readable in the end, - you can CTRL+click to navigate (in a decent IDE) on a method call,
not on an operator, - step-debugging will be a bumpy ride,
- some people will use them good, many will misuse them, some will do
evil with those, - and hell, if "->" doesn't mean "access that object member" anymore,
but "hey, the implementor could do anything at all instead", sky
will fall upon us.
I'm not sure the benefice-risk ratio is good enough.
That said, in the other side, I'm +1 with equals(), compareTo() and
those kind of magic methods for the "==" and comparison functions, this
is something that PHP lacks, but I'm unsure this worth making all
operators user-land overridable.
I'm always favorable to something that makes the code being semantically
and non-ambiguous to read, and in my opinion userland operators create
more mess than they solve, they basically encourage people to make
non-readable stupid shortcuts in their code.
Best regards,
Pierre