[VOTE] Userspace operator overloading

php.internals

Unnamed Person

6 years ago
Hi internals, I have opened voting on https://wiki.php.net/rfc/userspace_operator_overloading, which allows users to overload operators in their own classes. Voting closes on 2020-04-06. Regards, Jan Böhmer

Marco Pivetta

6 years ago
Hey Jan, Just posting here why I voted "no": it is not your implementation proposal, but rather the concept per-se that IMO shouldn't land in the language. Operator overloading makes call-site code reading extremely hard, and it makes the language much more complex for very little benefit. Everything suggested in the RFC can be done by using explicit arrows: `->` (method calls), which lead to expressively named methods and parameters. I have posted similar thoughts about `->__toString()` and `->toString()` when it comes to cast operations vs explicit calls at https://github.com/ShittySoft/symfony-live-berlin-2018-doctrine-tutorial/pull/3#issuecomment-460441229 Overall, without type classes and infix functions, operator overloading is, IMO, just messy. Greets, Marco Pivetta http://twitter.com/Ocramius http://ocramius.github.com/ On Mon, Mar 23, 2020 at 6:58 PM <jan.h.boehmer@gmx.de> wrote:

Sebastian Bergmann

6 years ago
Am 24.03.2020 um 11:03 schrieb Marco Pivetta:
> Just posting here why I voted "no": it is not your implementation proposal, > but rather the concept per-se that IMO shouldn't land in the language.
I voted "no" for the same reason.

Sebastian Bergmann

6 years ago
Am 24.03.2020 um 11:06 schrieb Sebastian Bergmann:
> I voted "no" for the same reason.
I changed my vote to "yes" because of Nikita's arguments.

Matthew Brown

6 years ago
I accept that it adds an extra level of understanding to the language – if you see function addAmounts($a, $b) { return $a + $b; } you no longer definitely know the answer will be numeric. However, I imagine that the sorts of people who will use this are _also_ the sorts of people who would add type annotations clearly – so you'd much more likely see function addAmounts(CurrencyAmount $a, CurrencyAmount $b) { return $a + $b; } where it's trivial to understand that there's userspace operator overloading going on. On Tue, 24 Mar 2020 at 06:04, Marco Pivetta <ocramius@gmail.com> wrote:

Nikita Popov

6 years ago
On Mon, Mar 23, 2020 at 6:58 PM <jan.h.boehmer@gmx.de> wrote:
> Hi internals, > > I have opened voting on > https://wiki.php.net/rfc/userspace_operator_overloading, which allows > users > to overload operators in their own classes. > > Voting closes on 2020-04-06. > > Regards, > Jan Böhmer >
To offer a counter-point, I voted yes on this proposal. A couple of thoughts: 1. This is exposing functionality that already exists for internal classes to userland classes. As a general rule, I think we should always strive to have behavioral parity between internal and userland classes. 2. Because this just exposes existing functionality, the amount of technical complexity this introduces is very small, especially compared to the significance of what this enables. The implementation complexity of RFCs is increasingly becoming a problem, due to the complex way in which existing language features interact. This RFC does not fall into that category. 3. As mentioned, this functionality already exists internally and is used by GMP, where it works (imho) very well. Of course, this is also something of a poster-child use case for operator overloading, in that the object literally represents a number. But there are plenty other similar examples, some of them very relevant to PHP (money represention). I think there is a somewhat irrational fear that operator overloading is going to be misused... but having worked in quite a few languages that do support operator overloading, I think I've only encountered this in two instances: First, the infamous use of << and >> as stream operators in C++ (which is really horrible, and gives operator overloading a bad name everywhere). And second, the use of / as a path concatenation operator in some libraries. And I think that's it. All other uses I've worked with were things like arbitrary-precision integers, floats, decimals or rationals; complex numbers, vectors, matrixes or tensors; bit vectors, constant ranges, known bits representations. Operator overloading is not a feature that is commonly needed or should be used much, but it does tend to make code a *lot* more readable in the cases where it is useful. I think people might also find this blog post by Guido interesting: https://neopythonic.blogspot.com/2019/03/why-operators-are-useful.html Regards, Nikita

Nicolas Grekas

6 years ago
> 1. This is exposing functionality that already exists for internal classes >
2. Because this just exposes existing functionality, the amount of
> technical complexity this introduces is very small >
> 3. As mentioned, this functionality already exists internally and is used > by GMP, where it works (imho) very well. >
Thanks Nikita for the insights, that's really helpful. I'd suggest removing the "PHP_OPERAND_TYPES_NOT_SUPPORTED" constant and settle on null instead. The reason is visible in the RFC: `public static function __mul($lhs, $rhs): ?Vector3` - nothing else than null can be returned here when looking at the return type. Returning a const is just calling for "WTF" indirections IMHO. I'm still voting yes, hoping for this const to be removed after if the RFC passes, if it does :) Nicolas

Mark Randall

6 years ago
On 23/03/2020 17:58, jan.h.boehmer@gmx.de wrote:
> I have opened voting on > https://wiki.php.net/rfc/userspace_operator_overloading, which allows users > to overload operators in their own classes.
I am certainly not opposed to operator overloading, and would like to see it in the language. However, I'll be voting no because of a couple of implementation points: 1. I feel like the choice of NULL as the return type for not implemented is far from ideal, especially for 8.0 which provides for union type hints. 2. The error handling behaviour is too timid. If operator overloading is to become a first-class feature, error handling should reflect as such and any attempt to use objects without the appropriate handlers being installed really should result in an error being thrown just as it would be if an undefined method was called. Mark Randall

Benjamin Eberlei

6 years ago
On Mon, Mar 23, 2020 at 6:58 PM <jan.h.boehmer@gmx.de> wrote:
> Hi internals, > > I have opened voting on > https://wiki.php.net/rfc/userspace_operator_overloading, which allows > users > to overload operators in their own classes. > > Voting closes on 2020-04-06. > > Regards, > Jan Böhmer >
Thank you. I voted yes on this for similar reasons than Nikita. I think it is confusing to users why internal objects can overload these and userland can't. The simplicity of exposing this is another plus. The potential for misuse is a non issue for me, because essentially this applies to most features, especially with existing magic methods we have a precedent that we can build upon instead of "repressing use".

Christoph Becker

6 years ago
On 23.03.2020 at 18:58, jan.h.boehmer@gmx.de wrote:
> I have opened voting on > https://wiki.php.net/rfc/userspace_operator_overloading, which allows users > to overload operators in their own classes.
It seems to me that the RFC is not sufficiently specific enough regarding the concatenation of instances of classes which implement __toString(). The RFC itself doesn't mention that explicitly, and the backward incompatible changes section states: | As long as the user does not implement, the operator magic functions, | operators on objects will behave in the previous way. However, at least the current implementation raises two notices ("You have to implement the __concat function") when concatenating Stringable objects. A minor issue: in my opinion, introducing PHP_OPERAND_TYPES_NOT_SUPPORTED doesn't make sense. The RFC says: | Handlers can specify return typehints, but note that the return type | has to be nullable (as PHP_OPERAND_TYPES_NOT_SUPPORTED has the value | null). So if we ever wanted to change the value of PHP_OPERAND_TYPES_NOT_SUPPORTED, we'd introduce a BC break. Therefore the constant's value likely will never change, so using NULL directly to signal unsupported operand types would be fine, wouldn't it?
-- Christoph M. Becker

Jakob Givoni

6 years ago
On Wed, Mar 25, 2020 at 6:28 AM Christoph M. Becker <cmbecker69@gmx.de> wrote:
> > It seems to me that the RFC is not sufficiently specific enough > regarding the concatenation of instances of classes which implement > __toString().
Exactly what I was thinking too. Would be nice with some examples on this.
> So if we ever wanted to change the value of > PHP_OPERAND_TYPES_NOT_SUPPORTED, we'd introduce a BC break. Therefore > the constant's value likely will never change, so using NULL directly to > signal unsupported operand types would be fine, wouldn't it?
Agree on this too.

Unnamed Person

6 years ago
On Thursday, March 26, 2020 3:50 AM Jakob Givoni <jakob@givoni.dk> wrote:
> On Wed, Mar 25, 2020 at 6:28 AM Christoph M. Becker <cmbecker69@gmx.de> wrote: > >> It seems to me that the RFC is not sufficiently specific enough >> regarding the concatenation of instances of classes which implement >> __toString(). > > Exactly what I was thinking too. Would be nice with some examples on this.
I am not sure if I can (or should) still add this to the RFC, here some clarification on this: The overloaded concat operator has higher priority than the __toString() method. So if Class A overloades the concat operator, then calling $a . $b means ClassA::__concat($a, $b); (Note that both operands are passed in their original form) If you want to concat the string representations, you will have to explicitly convert the objects to strings: $ret = (string) $a . (string) $b; If the concat operator is not overloaded, the behavior is like now, and the objects are converted implicitly to strings (so $a . $b actually means (string) $a . (string) $b). Furthermore an notice is triggered, hinting the user that he could overload the concat operator. (Maybe here a different message than for the other operators would be useful).

Guilliam Xavier

6 years ago
On Thu, Mar 26, 2020 at 5:37 PM <jan.h.boehmer@gmx.de> wrote:
> > The overloaded concat operator has higher priority than the __toString() method. > So if Class A overloades the concat operator, then calling $a . $b means ClassA::__concat($a, $b); (Note that both operands are passed in their original form) > If you want to concat the string representations, you will have to explicitly convert the objects to strings: > $ret = (string) $a . (string) $b;
This first part seems legit to me.
> If the concat operator is not overloaded, the behavior is like now, and the objects are converted implicitly to strings (so $a . $b actually means (string) $a . (string) $b). > Furthermore an notice is triggered, hinting the user that he could overload the concat operator. (Maybe here a different message than for the other operators would be useful).
I fear that "hint" notice could break Symfony apps... Couldn't you just not trigger it in this case?
-- Guilliam Xavier

Christoph Becker

6 years ago
On 26.03.2020 at 18:18, Guilliam Xavier wrote:
> On Thu, Mar 26, 2020 at 5:37 PM <jan.h.boehmer@gmx.de> wrote: >> >> The overloaded concat operator has higher priority than the __toString() method. >> So if Class A overloades the concat operator, then calling $a . $b means ClassA::__concat($a, $b); (Note that both operands are passed in their original form) >> If you want to concat the string representations, you will have to explicitly convert the objects to strings: >> $ret = (string) $a . (string) $b; > > This first part seems legit to me. > >> If the concat operator is not overloaded, the behavior is like now, and the objects are converted implicitly to strings (so $a . $b actually means (string) $a . (string) $b). >> Furthermore an notice is triggered, hinting the user that he could overload the concat operator. (Maybe here a different message than for the other operators would be useful). > > I fear that "hint" notice could break Symfony apps... Couldn't you > just not trigger it in this case?
Either that (it's what I would prefer), or clearly document that BC break (even if it's just about a notice). Thanks, Christoph

Unnamed Person

6 years ago
On Thursday, March 26, 2020 6:19 PM Guilliam Xavier <guilliam.xavier@gmail.com> wrote:
>> If the concat operator is not overloaded, the behavior is like now, and the objects are converted implicitly to strings (so $a . $b actually means (string) $a . (string) $b). >> Furthermore an notice is triggered, hinting the user that he could overload the concat operator. (Maybe here a different message than for the other operators would be useful). > > I fear that "hint" notice could break Symfony apps... Couldn't you just not trigger it in this case?
Yes this would be possible and I think that it might be reasonable to omit the notice in this case (maybe only if the objects really implement a __toString). What do others think about this? Regards, Jan

Alexander Lissachenko

6 years ago
Hi, internals! I want to mention, that all existing internal API of Zend could be accessible via FFI as of PHP7.4. This gives opportunity to implement userspace operator overloading as a simple PHP package. I know, that FFI requires some polishing, but it could become a tool to create interesting extensions via Z-Engine or similar libraries. Just for example, I have a repository https://github.com/lisachenko/native-types which provides an example of userspace operator-overloading for matrices (see __doOperation method implementation at https://github.com/lisachenko/native-types/blob/master/src/Matrix.php#L232-L276 ) Please share you thoughts about this. Best regards, Alexander пн, 23 мар. 2020 г. в 20:58, <jan.h.boehmer@gmx.de>:

Derick Rethans

6 years ago
On Mon, 23 Mar 2020, jan.h.boehmer@gmx.de wrote:
> Hi internals, > > I have opened voting on > https://wiki.php.net/rfc/userspace_operator_overloading, which allows users > to overload operators in their own classes.
I've always been a strong proponent of operator overloading, but as PHP already has it internally, it makes sense to also make that information available for userland. cheers, Derick

Derick Rethans

6 years ago
On Thu, 26 Mar 2020, Derick Rethans wrote:
> On Mon, 23 Mar 2020, jan.h.boehmer@gmx.de wrote: > > > I have opened voting on > > https://wiki.php.net/rfc/userspace_operator_overloading, which allows users > > to overload operators in their own classes. > > I've always been a strong proponent of operator overloading, but as PHP
^^^^^^^^^ I meant "opponent" there of course.
> already has it internally, it makes sense to also make that information > available for userland.
In any case, I am a +1! cheers, Derick
-- PHP 7.4 Release Manager Host of PHP Internals News: https://phpinternals.news Like Xdebug? Consider supporting me: https://xdebug.org/support https://derickrethans.nl | https://xdebug.org | https://dram.io twitter: @derickr and @xdebug

Michał Brzuchalski

6 years ago
pon., 23 mar 2020 o 18:58 <jan.h.boehmer@gmx.de> napisał(a):
> Hi internals, > > I have opened voting on > https://wiki.php.net/rfc/userspace_operator_overloading, which allows > users > to overload operators in their own classes. >
I got two comments, a little late but always better than even later. The first thing is for operator methods when the operation is not supported I would see simply `return null;` as the right solution instead of constant, which name no one will remember. The second thing is notices which are confusing when suggesting to go and implement overloaded operator method on extension derived classes like stdClass like below: Notice: You have to implement the __add function in class stdClass to use this operator with an object in... The text of the notice is not documented in the RFC but it is implemented that way in the patch. A PHP developer is not likely gonna download the php-src source code, build environment and go with the ext/standard implementation and start adding it in C. The notice in this cases should either be different or not emitted at all. If the latter then that has to be documented I guess. Cheers,
-- Michał Brzuchalski

Andrew Faulds

6 years ago
Hi, Michał Brzuchalski wrote:
> The first thing is for operator methods when the operation is not supported > I would see simply `return null;` as the right solution > instead of constant, which name no one will remember.
If we are to allow arbitrary use of operator overloading (rather than restricting it to specific kinds of use), which this RFC does, then IMHO we shouldn't special-case “null” — why can't it be a valid result of an overloaded operator? It is an arbitrary constraint on what can be done with operator overloading and makes the feature less generic. I would prefer if we used a different mechanism, for example a special subclass of Exception which will be caught internally by the operator overloading code, so that signalling an operation is unsupported can be “out of band” if that makes sense.
> The second thing is notices which are confusing when suggesting to go > and implement overloaded operator method on extension derived classes > like stdClass like below: > > Notice: You have to implement the __add function in class stdClass to use > this operator with an object in... > > The text of the notice is not documented in the RFC but it is implemented > that way in the patch.
Please document all notices your patch produces in the RFC, I think they are an important detail of the proposal.
> A PHP developer is not likely gonna download the php-src source code, > build environment and go with the ext/standard implementation and start > adding it in C. > The notice in this cases should either be different or not emitted at all. > If the latter then that has to be documented I guess.
That sounds reasonable, and it should be simple to add a check to your patch to make sure it won't show this message for internal/extension classes, right? By the way, if the operation has two operands, one of which is an internal/extension class and the other a user class, you could perhaps still show the mssage, but only mention the user class name. Regars, Andrea

Johannes Schlueter

6 years ago
On Mon, 2020-03-23 at 18:58 +0100, jan.h.boehmer@gmx.de wrote:
> Hi internals, > > I have opened voting on > https://wiki.php.net/rfc/userspace_operator_overloading, which allows > users to overload operators in their own classes. >
I consider operator overlaoding in general a good feature (while PHP's overload for + on arrays is weird) However I don't like this design. Consider this case: Some library provides a type A without overloads. I then create a type B with overload: class B { static public __add($lhs, $rhs) { if ($lhs instanceof A) ... elseif($rhs instanceof A) ... else error } } as I want my type's addition to commutative I handle this with some if (probably dispatching. Now I can invoke it: new A() + new B(); // calls B::__add() new B() + new A(); // calls B::__add() as well Now the maintainer of A implements the feature request "A should allow addition with integers", I update my library and suddenly weird things happen. new A() + new B(); // calls A::__add() and explodes, as A doesn't know B new B() + new A(); // calls B::__add() works as before Now we could establish the best practice that one type's operator calls the other type's operator in case it can't handle a type. However that is no good option as it will lead to infinite recursion if neither type can handle both operands. The issue is that by forcing type declarations as part of the class, as members, this forms a closed set, but for many cases a strict closed set isn't what one wants. For mathematical types having operators working with integers (and other numeric values) is essential. Communativity often also is required. So a true closed set doesn't work, for an open set can't be created easily in today's PHP. Thus the RFC tries to tie this together, but this will fall apart and then cause legacy and BC reasons preventing future improvement. I believe the pre-requisit is having some form of function overloading, where operator functions for specific argument types can be defined. In https://news-web.php.net/php.internals/108425 Andrea created an idea, which is probably "ugly" but has less usage restrictions. I think spending time on function overloading (I believe, without proving it, this can be done with very little cost for non-overlaoded cases - by adding a flag "overloaded" along visibility flags and check that along with the visibility check, only in case of an overload to the "expensive" check, which still is cheaper done in the engine than if/else chains in userspace) and then take up operator overloading again, rather than this smart but limited approach. (For whoever does that: spend time in C++ and its function resolution rules incl. ADL, not to copy, but to learn) johannes

Arnold Daniels

6 years ago
> > > On Mon, 2020-03-23 at 18:58 +0100, jan.h.boehmer@gmx.de wrote: > > Hi internals, > > > > I have opened voting on > > https://wiki.php.net/rfc/userspace_operator_overloading, which allows > > users to overload operators in their own classes. > > > > I consider operator overlaoding in general a good feature (while PHP's > overload for + on arrays is weird) > > However I don't like this design. > > Consider this case: Some library provides a type A without overloads. I > then create a type B with overload: > > class B { > static public __add($lhs, $rhs) { > if ($lhs instanceof A) ... > elseif($rhs instanceof A) ... > else error > } > } > > as I want my type's addition to commutative I handle this with some if > (probably dispatching. Now I can invoke it: > > new A() + new B(); // calls B::__add() > new B() + new A(); // calls B::__add() as well > > Now the maintainer of A implements the feature request "A should allow > addition with integers", I update my library and suddenly weird things > happen. > > new A() + new B(); // calls A::__add() and explodes, as A doesn't know > B > new B() + new A(); // calls B::__add() works as before > > Now we could establish the best practice that one type's operator calls > the other type's operator in case it can't handle a type. However that > is no good option as it will lead to infinite recursion if neither type > can handle both operands. > > > The issue is that by forcing type declarations as part of the class, as > members, this forms a closed set, but for many cases a strict closed > set isn't what one wants. For mathematical types having operators > working with integers (and other numeric values) is essential. > Communativity often also is required. So a true closed set doesn't > work, for an open set can't be created easily in today's PHP. Thus the > RFC tries to tie this together, but this will fall apart and then cause > legacy and BC reasons preventing future improvement. > > I believe the pre-requisit is having some form of function overloading, > where operator functions for specific argument types can be defined. In > https://news-web.php.net/php.internals/108425 Andrea created an idea, > which is probably "ugly" but has less usage restrictions. I think > spending time on function overloading (I believe, without proving it, > this can be done with very little cost for non-overlaoded cases - by > adding a flag "overloaded" along visibility flags and check that along > with the visibility check, only in case of an overload to the > "expensive" check, which still is cheaper done in the engine than > if/else chains in userspace) and then take up operator overloading > again, rather than this smart but limited approach. (For whoever does > that: spend time in C++ and its function resolution rules incl. ADL, > not to copy, but to learn) > > johannes > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: http://www.php.net/unsub.php > >
This issues become even more apparent when sequencing operations like `$a + $b + $c - $d`. Trying left, then trying right, will make it very difficult to determine the outcome of such a statement. The arguments against type hinting for operator methods, assume the "try left/right" method. Instead, type hinting should be applied to determine which method should be used. If both or neither methods are applicable, an error must be thrown. With class inheritance, you also don't want to rely on the order of the operands. If you're the creator of a library, that's out of your hands. If both the parent and child class overwrite the operation, and both accept the operands, then the child class should always be used. It's a -1 for me. I would vote in favor of an RFC for operator overloading without the "try left, try right" behavior. Arnold

Christoph Becker

6 years ago
On 28.03.2020 at 10:22, Arnold Daniels wrote:
> This issues become even more apparent when sequencing operations like `$a + > $b + $c - $d`. Trying left, then trying right, will make it very difficult > to determine the outcome of such a statement. > > The arguments against type hinting for operator methods, assume the "try > left/right" method. Instead, type hinting should be applied to determine > which method should be used. If both or neither methods are applicable, an > error must be thrown.
This "try left/right" approach is how operator overloading works for internal classes[1], and apparently, it works quite well, as long as it is not overused.
> With class inheritance, you also don't want to rely on the order of the > operands. If you're the creator of a library, that's out of your hands. If > both the parent and child class overwrite the operation, and both accept > the operands, then the child class should always be used.
If a subclass overrides the operation, it would have to conform to parameter type contravariance, so it would always be used, anyway. [1] <https://github.com/php/php-src/blob/php-7.4.4/Zend/zend_operators.h#L934-L937>
-- Christoph M. Becker

Andrew Faulds

6 years ago
Hi, Christoph M. Becker wrote:
> On 28.03.2020 at 10:22, Arnold Daniels wrote: > >> This issues become even more apparent when sequencing operations like `$a + >> $b + $c - $d`. Trying left, then trying right, will make it very difficult >> to determine the outcome of such a statement. >> >> The arguments against type hinting for operator methods, assume the "try >> left/right" method. Instead, type hinting should be applied to determine >> which method should be used. If both or neither methods are applicable, an >> error must be thrown. > > This "try left/right" approach is how operator overloading works for > internal classes[1], and apparently, it works quite well, as long as it > is not overused.
I think “as long as it is not overused” are the key words there. We have a very limited number of internal classes with operator overloading right now, and the authors know about what other such classes exist and therefore can design with them in mind. But once any userland PHP class can use operator overloading, you could start seeing uses of operators where the left- and right-hand side are classes from unrelated libraries whose authors were not aware of eachother, and that could create strange problems when one or the other library is updated with operator overload handling. Consider Arnold's example: $a + $b + $c - $d We are not constraining operator overloading to just be for number-like objects, it can in principle be used for absolutely anything. $a here might have an overload that works for any right-hand-side object, but then once $b adds its own similar overload in a new version, the code no longer does the same thing when it's flipped around ($b + $a), violating the normal expectation that `+` is commutative, and that's before considering what $c and $d might do! Thanks, Andrea

Christoph Becker

6 years ago
On 28.03.2020 at 14:29, Andrea Faulds wrote:
> Christoph M. Becker wrote: > >> This "try left/right" approach is how operator overloading works for >> internal classes[1], and apparently, it works quite well, as long as it >> is not overused. > > I think “as long as it is not overused” are the key words there. We have > a very limited number of internal classes with operator overloading > right now, and the authors know about what other such classes exist and > therefore can design with them in mind. But once any userland PHP class > can use operator overloading, you could start seeing uses of operators > where the left- and right-hand side are classes from unrelated libraries > whose authors were not aware of eachother, and that could create strange > problems when one or the other library is updated with operator overload > handling. > > Consider Arnold's example: > >     $a + $b + $c - $d > > We are not constraining operator overloading to just be for number-like > objects, it can in principle be used for absolutely anything.
And floats can be used to store monetary values, and to do calculations, and apparently, that is actually done by some. Was it a bad idea to introduce floats to the language?
> $a here > might have an overload that works for any right-hand-side object, but > then once $b adds its own similar overload in a new version, the code no > longer does the same thing when it's flipped around ($b + $a), violating > the normal expectation that `+` is commutative, and that's before > considering what $c and $d might do!
If, for example, the + operator is overloaded to add something to a collection, the normal expectation that + is commutative is already violated. Operator overloading should definitely not be used for "anything", but only for those rare cases which resemble math operations (frankly, I would not have not supported __concat() at all). Also, overloaded operators should be programmed defensively, i.e. they should not accept arbitrary arguments (how could that even work?), but only those they can handle. If implementations adhere to these "rules", I don't see real issues.
-- Christoph M. Becker

Andrew Faulds

6 years ago
Hi, Christoph M. Becker wrote:
> (frankly, I would not have not supported __concat() at all). Also, > overloaded operators should be programmed defensively, i.e. they should > not accept arbitrary arguments (how could that even work?), but only > those they can handle. If implementations adhere to these "rules", I > don't see real issues.
Consider a type implementing some kind of list. Perhaps someone would want to overload the + operator to mean adding an item to the list. If the list accepts any type of value as a valid item, then you have an example of an unconditional overload. Regards, Andrea

Christoph Becker

6 years ago
On 28.03.2020 at 22:21, Andrea Faulds wrote:
> Christoph M. Becker wrote: >> (frankly, I would not have not supported __concat() at all).  Also, >> overloaded operators should be programmed defensively, i.e. they should >> not accept arbitrary arguments (how could that even work?), but only >> those they can handle.  If implementations adhere to these "rules", I >> don't see real issues. > > Consider a type implementing some kind of list. Perhaps someone would > want to overload the + operator to mean adding an item to the list. If > the list accepts any type of value as a valid item, then you have an > example of an unconditional overload.
I had already considered this, and wrote immediately above the quote:
> If, for example, the + operator is overloaded to add something to a > collection, the normal expectation that + is commutative is already > violated. Operator overloading should definitely not be used for > "anything", but only for those rare cases which resemble math > operations
Thanks, Christoph

Johannes Schlueter

6 years ago
On March 28, 2020 1:25:11 PM GMT+01:00, "Christoph M. Becker" <cmbecker69@gmx.de> wrote:
>On 28.03.2020 at 10:22, Arnold Daniels wrote: > >> This issues become even more apparent when sequencing operations like >`$a + >> $b + $c - $d`. Trying left, then trying right, will make it very >difficult >> to determine the outcome of such a statement. >> >> The arguments against type hinting for operator methods, assume the >"try >> left/right" method. Instead, type hinting should be applied to >determine >> which method should be used. If both or neither methods are >applicable, an >> error must be thrown. > >This "try left/right" approach is how operator overloading works for >internal classes[1], and apparently, it works quite well, as long as it >is not overused.
The fact that it works in one or two cases as an implementation detail where the full implementation is controlled by a single group (internals) is no indication for it to work at large.
>> With class inheritance, you also don't want to rely on the order of >>the
Inheritance is always complex. Yes operator handling (especially if type dispatching happens manually) will add a few things to think about. Certainly an interesting field to explore, before one votes. I myself consider larger inheritance structures a thing of the 90ies and for special cases like GUI widgets ... but yeah, language design has to consider it. Changing languages features like discussed here in future is hard. johannes

Christoph Becker

6 years ago
On 28.03.2020 at 15:57, Johannes Schlüter wrote:
> On March 28, 2020 1:25:11 PM GMT+01:00, "Christoph M. Becker" <cmbecker69@gmx.de> wrote: > >> This "try left/right" approach is how operator overloading works for >> internal classes[1], and apparently, it works quite well, as long as it >> is not overused. > > The fact that it works in one or two cases as an implementation detail where the full implementation is controlled by a single group (internals) is no indication for it to work at large.
Fair enough. But maybe Python, where userland operator overloading works similar to the proposal at hand, is? :) Furthermore, it is already possible to do operator overloading in userland by using the FFI extension[1], and given that operator overloading appears to be desired by many developers, I'd rather add it to the language, then having to deal with libraries which do it via FFI. [1] <https://github.com/lisachenko/z-engine#object-extensions-api>
-- Christoph M. Becker

Johannes Schlueter

6 years ago
On Sat, 2020-03-28 at 17:05 +0100, Christoph M. Becker wrote:
> On 28.03.2020 at 15:57, Johannes Schlüter wrote: > > > On March 28, 2020 1:25:11 PM GMT+01:00, "Christoph M. Becker" < > > cmbecker69@gmx.de> wrote: > > > > > This "try left/right" approach is how operator overloading works > > > for > > > internal classes[1], and apparently, it works quite well, as long > > > as it > > > is not overused. > > > > The fact that it works in one or two cases as an implementation > > detail where the full implementation is controlled by a single > > group (internals) is no indication for it to work at large. > > Fair enough. But maybe Python, where userland operator overloading > works similar to the proposal at hand, is? :)
It doesn't: Python 3.6.9 (default, Nov 7 2019, 10:44:02) [GCC 8.3.0] on linux Type "help", "copyright", "credits" or "license" for more information.
>>> class A:
... def __add__(self, other): ... print("add") ...
>>> a = A() >>> a + 1
add
>>> 1 + a
Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for +: 'int' and 'A' johannes

Nikita Popov

6 years ago
On Sat, Mar 28, 2020 at 5:28 PM Johannes Schlüter <johannes@schlueters.de> wrote:
> On Sat, 2020-03-28 at 17:05 +0100, Christoph M. Becker wrote: > > On 28.03.2020 at 15:57, Johannes Schlüter wrote: > > > > > On March 28, 2020 1:25:11 PM GMT+01:00, "Christoph M. Becker" < > > > cmbecker69@gmx.de> wrote: > > > > > > > This "try left/right" approach is how operator overloading works > > > > for > > > > internal classes[1], and apparently, it works quite well, as long > > > > as it > > > > is not overused. > > > > > > The fact that it works in one or two cases as an implementation > > > detail where the full implementation is controlled by a single > > > group (internals) is no indication for it to work at large. > > > > Fair enough. But maybe Python, where userland operator overloading > > works similar to the proposal at hand, is? :) > > It doesn't: > > > Python 3.6.9 (default, Nov 7 2019, 10:44:02) > [GCC 8.3.0] on linux > Type "help", "copyright", "credits" or "license" for more information. > >>> class A: > ... def __add__(self, other): > ... print("add") > ... > >>> a = A() > >>> a + 1 > add > >>> 1 + a > Traceback (most recent call last): > File "<stdin>", line 1, in <module> > TypeError: unsupported operand type(s) for +: 'int' and 'A' >
It does. Because Python uses instance methods for operator overloading, it has to use pairs of methods like __add__ and __radd__ to handle commuted variants. If you want to implement a commutative add, you need to implement both __add__ and __radd__. If __add__ is not implemented or returns NotImplemented, then __radd__ will be called. So, as Christoph has said, this is indeed the same system as what is being proposed here, with the difference that we do not need two separate methods per operator, but can use a static method instead. Regards, Nikita

Johannes Schlueter

6 years ago
On March 28, 2020 5:44:28 PM GMT+01:00, Nikita Popov <nikita.ppv@gmail.com> wrote:
>On Sat, Mar 28, 2020 at 5:28 PM Johannes Schlüter ><johannes@schlueters.de> >wrote: > >> On Sat, 2020-03-28 at 17:05 +0100, Christoph M. Becker wrote: >> > On 28.03.2020 at 15:57, Johannes Schlüter wrote: >> > >> > > On March 28, 2020 1:25:11 PM GMT+01:00, "Christoph M. Becker" < >> > > cmbecker69@gmx.de> wrote: >> > > >> > > > This "try left/right" approach is how operator overloading >works >> > > > for >> > > > internal classes[1], and apparently, it works quite well, as >long >> > > > as it >> > > > is not overused. >> > > >> > > The fact that it works in one or two cases as an implementation >> > > detail where the full implementation is controlled by a single >> > > group (internals) is no indication for it to work at large. >> > >> > Fair enough. But maybe Python, where userland operator overloading >> > works similar to the proposal at hand, is? :) >> >> It doesn't: >> >> >> Python 3.6.9 (default, Nov 7 2019, 10:44:02) >> [GCC 8.3.0] on linux >> Type "help", "copyright", "credits" or "license" for more >information. >> >>> class A: >> ... def __add__(self, other): >> ... print("add") >> ... >> >>> a = A() >> >>> a + 1 >> add >> >>> 1 + a >> Traceback (most recent call last): >> File "<stdin>", line 1, in <module> >> TypeError: unsupported operand type(s) for +: 'int' and 'A' >> > >It does. > >Because Python uses instance methods for operator overloading, it has >to >use pairs of methods like __add__ and __radd__ to handle commuted >variants. >If you want to implement a commutative add, you need to implement both >__add__ and __radd__. If __add__ is not implemented or returns >NotImplemented, then __radd__ will be called. > >So, as Christoph has said, this is indeed the same system as what is >being >proposed here, with the difference that we do not need two separate >methods >per operator, but can use a static method instead.
If we use two methods as well it is a saner design. I won't like it, but lot better than the current one. johannes

Andrew Faulds

6 years ago
Hi everyone, Johannes Schlüter wrote:
> > If we use two methods as well it is a saner design. I won't like it, but lot better than the current one.
Just want to +1 this. Two methods, neither of which are static, seems like a cleaner approach to me. I maybe like this better than my suggested boolean argument (it certainly looks nicer). Consider what happens in the current proposal if a class, intentionally or otherwise, only supports being on one side of an operator… it will produce the same error message as if it didn't support the overload at all :( Thanks, Andrea

Unnamed Person

6 years ago
Hi internals, I have closed the voting. With 38 in favor and 28 against the RFC is DECLINED (didn’t reach the needed 2/3 majority for a new feature). Thanks to everyone who has participated.

Sebastian Bergmann

6 years ago
Am 27.03.2020 um 21:10 schrieb Johannes Schlüter:
> However I don't like this design. > [...]
Changed my vote back to "no" based on Johannes' arguments.

Andrew Faulds

6 years ago
Hi Johannes, Johannes Schlüter wrote:
> I believe the pre-requisit is having some form of function overloading, > where operator functions for specific argument types can be defined. In > https://news-web.php.net/php.internals/108425 Andrea created an idea, > which is probably "ugly" but has less usage restrictions. I think > spending time on function overloading (I believe, without proving it, > this can be done with very little cost for non-overlaoded cases - by > adding a flag "overloaded" along visibility flags and check that along > with the visibility check, only in case of an overload to the > "expensive" check, which still is cheaper done in the engine than > if/else chains in userspace) and then take up operator overloading > again, rather than this smart but limited approach. (For whoever does > that: spend time in C++ and its function resolution rules incl. ADL, > not to copy, but to learn)
Thinking about what I suggested there again now, I think it's a shame this RFC includes the following restriction:
> The argument must not specify any argument typehints (an error is
thrown otherwise), as typehints and occuring type errors would break operator evaluation (see discussion). With strict evaluation of union types, it would be possible to achieve something like what I suggested in a less ugly form: class A { /* … */ public function __add(int|float|B $other) { /* … */ } } class B { /* … */ public function __add(int|C $other) { /* … */ } } From the union typs in this example, the interpreter could see, without having to execute either overloaded method, that A provides an overload for B, int and float, and B provides an overload for C and int, and so it knows the same method can be called for both `$a + $b` and `$b + $a`, likewise for `$b + $c` and `$c + $b`. Of course, this requires interpreting type declarations in an unusual way, and it also needs a solution to non-commutative operators (perhaps a second parameter that would contain a boolean specifying whether `$this` is on the left), but I think it is less likely to be chaotic than the current proposal. Thanks, Andrea