Re: [RFC] Userspace operator overloading

php.internals

Unnamed Person

6 years ago
On 15/02/2020 22:05, jan.h.boehmer@gmx.de wrote:
> Hi internals, > > based on the discussions here (https://externals.io/message/108300) and
here
> (https://github.com/php/php-src/pull/5156), I have created a proper RFC
for
> userspace operator overloading: > https://wiki.php.net/rfc/userspace_operator_overloading > > The main differences to my original concept, is the removed __compare() > method (comparison overloading is a complex topic and should be handled in
a
> different RFC) and the possibility to signal that the operator handler
does
> not support the given types (by typehints or returning a special value). > This way, only one of both objects has to know about the other type. This > should expand the use case of operator overloading compared to my old > concept. > > What do you think about the RFC? > > Some discussion points, I can think of, would be the naming of the methods > (maybe naming them after the operator symbol and not the arithmetical > operation they represent, e.g. __plus instead of __add) or putting the > methods inside of interfaces like done for ArrayAccess (But I don’t see
any
> advantage in doing so, as it is very difficult grouping different
operators
> in a single interface usefully. Also, operators can accept and return > different types, so there is no real common interface between classes you > could rely on). > Furthermore, maybe the idea of allowing operator overloading in general > should be discussed as it is sometimes considered an anti-pattern (e.g.
the
> usage of '<<' for outputting a string in C++). On the other hand there are > many languages and libraries where operator overloading is used
successfully
> (e.g. numpy in Python). > > Regards, > Jan Böhmer
I have changed the proposed names for the bitshift handlers to '__lshift' and '__rshift' (instead of __sl and __sr) to make more clear what operator is handled by the method (also the method names are now mostly consistent with the Python equivalents). How many of you would prefer a interface solution for operator overloading? I wonder if the RFC voting should include the option to choose between either the magic method approach (with the syntax proposed in the current RFC version) or using interfaces. For an interface version I would suggest these interfaces: ArithmeticOperators (implements +, -, *, /), PowOperator (**), ModuloOperator (%), ConcatOperator (.) and BitwiseOperators (~, &, |, ^, <<,
>>).
What would be appropriate names for the interface methods? If we just name them after the operation (like add()), it will become difficult to integrate these interfaces into existing code, as add() is already a used function name in many cases, but uses a different signature (non-static with one argument, whereas the interface needs a static one with two arguments). Regards, Jan

Andrew Faulds

6 years ago
Hi Jan, jan.h.boehmer@gmx.de wrote:
> On 15/02/2020 22:05, jan.h.boehmer@gmx.de wrote: > How many of you would prefer a interface solution for operator overloading? > I wonder if the RFC voting should include the option to choose between > either the magic method approach (with the syntax proposed in the current > RFC version) or using interfaces. > For an interface version I would suggest these interfaces: > ArithmeticOperators (implements +, -, *, /), PowOperator (**), > ModuloOperator (%), ConcatOperator (.) and BitwiseOperators (~, &, |, ^, <<, >>> ).
I have probably already said this on this list; I like the idea of interfaces as they discourage abuse. I'm not sure if I like those particular groupings however: * For integer-like types maybe supporting + - * without / would make sense, since / is de-facto floating-point division right now * Maybe / and ** should be grouped? * It's a shame intdiv() isn't an operator, as it would be grouped with % I guess… perhaps % and / should be grouped, if we expect types to use / for pure integer division? (This kinda contradicts my earlier point) * If the bitwise operators were used for sets (Python does this), you would use & | ^ but not ~ << >> Maybe the typeclasses in the Haskell Prelude can provide some inspiration (https://hackage.haskell.org/package/base-4.12.0.0/docs/Prelude.html has Num, Integral, Fractional, Floating), but this should probably be fashioned after imagined use-cases: what kinds of types can we imagine using operator overloading, and what sets of operators would they implement? From those, reasonable groups could be formed, I guess.
> What would be appropriate names for the interface methods? If we just name > them after the operation (like add()), it will become difficult to integrate > these interfaces into existing code, as add() is already a used function > name in many cases, but uses a different signature (non-static with one > argument, whereas the interface needs a static one with two arguments).
I had wanted just ->add(), but you raise a good point that existing code would probably conflict. An easy solution could be to prefix all the methods with “operator”? ->operatorAdd(), ->operatorMinus(), ->operatorDivide(), ->operatorBitwiseNot() (to distinguish it from hypothetical future LogicalNot/BooleanNot) etc… Thanks, Andrea

Nikita Popov

6 years ago
On Thu, Feb 27, 2020 at 9:43 PM <jan.h.boehmer@gmx.de> wrote:
> On 15/02/2020 22:05, jan.h.boehmer@gmx.de wrote: > > Hi internals, > > > > based on the discussions here (https://externals.io/message/108300) and > here > > (https://github.com/php/php-src/pull/5156), I have created a proper RFC > for > > userspace operator overloading: > > https://wiki.php.net/rfc/userspace_operator_overloading > > > > The main differences to my original concept, is the removed __compare() > > method (comparison overloading is a complex topic and should be handled > in > a > > different RFC) and the possibility to signal that the operator handler > does > > not support the given types (by typehints or returning a special value). > > This way, only one of both objects has to know about the other type. This > > should expand the use case of operator overloading compared to my old > > concept. > > > > What do you think about the RFC? > > > > Some discussion points, I can think of, would be the naming of the > methods > > (maybe naming them after the operator symbol and not the arithmetical > > operation they represent, e.g. __plus instead of __add) or putting the > > methods inside of interfaces like done for ArrayAccess (But I don’t see > any > > advantage in doing so, as it is very difficult grouping different > operators > > in a single interface usefully. Also, operators can accept and return > > different types, so there is no real common interface between classes you > > could rely on). > > Furthermore, maybe the idea of allowing operator overloading in general > > should be discussed as it is sometimes considered an anti-pattern (e.g. > the > > usage of '<<' for outputting a string in C++). On the other hand there > are > > many languages and libraries where operator overloading is used > successfully > > (e.g. numpy in Python). > > > > Regards, > > Jan Böhmer > > I have changed the proposed names for the bitshift handlers to '__lshift' > and '__rshift' (instead of __sl and __sr) to make more clear what operator > is handled by the method (also the method names are now mostly consistent > with the Python equivalents). > > How many of you would prefer a interface solution for operator overloading? > I wonder if the RFC voting should include the option to choose between > either the magic method approach (with the syntax proposed in the current > RFC version) or using interfaces. > For an interface version I would suggest these interfaces: > ArithmeticOperators (implements +, -, *, /), PowOperator (**), > ModuloOperator (%), ConcatOperator (.) and BitwiseOperators (~, &, |, ^, > <<, > >>). > What would be appropriate names for the interface methods? If we just name > them after the operation (like add()), it will become difficult to > integrate > these interfaces into existing code, as add() is already a used function > name in many cases, but uses a different signature (non-static with one > argument, whereas the interface needs a static one with two arguments). > > Regards, > Jan >
Some notes: Naming: If we're already going for more verbose names, I'd prefer __shiftLeft over __lshift. It may also make sense to use __bitwiseNot rather than __not, as it's currently not obviously whether it overloads the ~ or the ! operator. Similarly __bitwiseAnd could be better than __and, to make it clear that this is about & and not && or "and". Same for the other bitwise operators.
> -$a is interpreted as (-1 * $a).
As you mention this, for completeness: +$a is interpreted as (1 * $a). It may be worthwhile to give a more explicit list for a) operators that can be indirectly overloaded (and their desugaring) and b) operators that cannot be overloaded (like the boolean operators, comparison operators, instanceof, probably others).
> If the operator handler function declares typehints (e.g. public static
function __add(Vector3 $lhs, int $rhs)), the function handler is not called if the operand types do not match the signature, and the other operand's handler is tried to call. I'm somewhat skeptical about this. This smells of method overloading, and we don't do method overloading. There is no other place in PHP that would perform dispatching based on the method signature, even if in this case it's not a choice between multiple methods on the same class, but rather multiple methods on different classes. Some of the usual problems of method overloading don't apply here (in particular, one could make a reasonable argument that the method on the first object should be chosen, even if there is a more specific signature available on the second object), but I'm skeptical about introducing this kind of special case in the language specification. Regarding interfaces: I pretty strongly think that using interfaces for this purpose is not a good idea. Interfaces are about contracts, and there is no way (within the current limitations of PHP's type system) to define a useful contract here. I think others have already expanded on why it's not possible to group operators in a meaningful way. The DateTime example is probably the most pertinent there, in that DateTime + DateTime is illegal, while DateTime - DateTime is legal. If we can't even require both + and - as part of one interface, there is very little we can require. However, even if we split each method into it's own interface, what we'll be left with is something like interface Add { public static function add($a, $b); } What does this interface tell us? Pretty much nothing. It tells us that there probably exists at least one type with which this object can be added, but not what that type actually is or what the result type would be. There is no way to build code on the contract of "the object is addable with *something*". To make this useful, the interface would actually have to look something like this: interface Add<T1, T2, T3> { public static function add(T1 $a, T2 $b): T3; } and then DateTime would implement something like this: class DateTime implements Sub<DateTime, DateTime, DateInterval>, Add<DateTime, DateInterval, DateTime> { ... } That's a contract you could build code on, though it would be rather cumbersome. But in the absence of generic types, having an Add interface is not useful. The way I see operator overloading being used in PHP, I would expect code to be typed against specific classes, not supported operators. The vast majority of code will be interested in accepting a Money, not an Add. Regards, Nikita

Rowan Collins

6 years ago
On Mon, 2 Mar 2020 at 14:30, Nikita Popov <nikita.ppv@gmail.com> wrote:
> > I think others have already expanded on why it's not possible to group > operators in a meaningful way. The DateTime example is probably the most > pertinent there, in that DateTime + DateTime is illegal, while DateTime - > DateTime is legal. If we can't even require both + and - as part of one > interface, there is very little we can require. >
In my opinion, not being able to overload both + and - is a sign that the class is "abusing" operator overloading, in that it is using it to build a domain-specific language, rather than truly overloading *operations*. An overload of "DateTime - DateTime" wouldn't represent "subtraction" for precisely the reason that "DateTime + DateTime" doesn't make sense - DateTime doesn't follow arithmetical logic. Instead, it would represent a domain-specific definition of "difference". A more "correct" operator overloading would be DateTime + DateInterval, and its inverse DateTime - DateInterval. The common consensus seems to be that we don't want domain-specific overloading (the example of << and >> in C++ has been mentioned several times). If that's the case, then an interface that prevents you implementing DateTime - DateTime seems perfectly legitimate. Regards,
-- Rowan Tommins [IMSoP]

Mike Schinkel

6 years ago
> On Mar 2, 2020, at 9:30 AM, Nikita Popov <nikita.ppv@gmail.com> wrote: > > On Thu, Feb 27, 2020 at 9:43 PM <jan.h.boehmer@gmx.de> wrote: > >> I have changed the proposed names for the bitshift handlers to '__lshift' >> and '__rshift' (instead of __sl and __sr) to make more clear what operator >> is handled by the method (also the method names are now mostly consistent >> with the Python equivalents). >> > > Naming: If we're already going for more verbose names, I'd prefer > __shiftLeft over __lshift.
Yes, please. -Mike

Unnamed Person

6 years ago
Am 02.03.2020 um 15:30 schrieb Nikita Popov:
> On Thu, Feb 27, 2020 at 9:43 PM <jan.h.boehmer@gmx.de > <mailto:jan.h.boehmer@gmx.de>> wrote: > > On 15/02/2020 22:05, jan.h.boehmer@gmx.de > <mailto:jan.h.boehmer@gmx.de> wrote: > > Hi internals, > > > > based on the discussions here > (https://externals.io/message/108300) and > here > > (https://github.com/php/php-src/pull/5156), I have created a > proper RFC > for > > userspace operator overloading: > > https://wiki.php.net/rfc/userspace_operator_overloading > > > > The main differences to my original concept, is the removed > __compare() > > method (comparison overloading is a complex topic and should be > handled in > a > > different RFC) and the possibility to signal that the operator > handler > does > > not support the given types (by typehints or returning a special > value). > > This way, only one of both objects has to know about the other > type. This > > should expand the use case of operator overloading compared to > my old > > concept. > > > > What do you think about the RFC? > > > > Some discussion points, I can think of, would be the naming of > the methods > > (maybe naming them after the operator symbol and not the > arithmetical > > operation they represent, e.g. __plus instead of __add) or > putting the > > methods inside of interfaces like done for ArrayAccess (But I > don’t see > any > > advantage in doing so, as it is very difficult grouping different > operators > > in a single interface usefully. Also, operators can accept and > return > > different types, so there is no real common interface between > classes you > > could rely on). > > Furthermore, maybe the idea of allowing operator overloading in > general > > should be discussed as it is sometimes considered an > anti-pattern (e.g. > the > > usage of '<<' for outputting a string in C++). On the other hand > there are > > many languages and libraries where operator overloading is used > successfully > > (e.g. numpy in Python). > > > > Regards, > > Jan Böhmer > > I have changed the proposed names for the bitshift handlers to > '__lshift' > and '__rshift' (instead of __sl and __sr) to make more clear what > operator > is handled by the method (also the method names are now mostly > consistent > with the Python equivalents). > > How many of you would prefer a interface solution for operator > overloading? > I wonder if the RFC voting should include the option to choose between > either the magic method approach (with the syntax proposed in the > current > RFC version) or using interfaces. > For an interface version I would suggest these interfaces: > ArithmeticOperators (implements +, -, *, /), PowOperator (**), > ModuloOperator (%), ConcatOperator (.) and BitwiseOperators (~, &, > |, ^, <<, > >>). > What would be appropriate names for the interface methods? If we > just name > them after the operation (like add()), it will become difficult to > integrate > these interfaces into existing code, as add() is already a used > function > name in many cases, but uses a different signature (non-static > with one > argument, whereas the interface needs a static one with two > arguments). > > Regards, > Jan > > > Some notes: > > Naming: If we're already going for more verbose names, I'd prefer > __shiftLeft over __lshift. It may also make sense to use __bitwiseNot > rather than __not, as it's currently not obviously whether it > overloads the ~ or the ! operator. Similarly __bitwiseAnd could be > better than __and, to make it clear that this is about & and not && or > "and". Same for the other bitwise operators. > > > -$a is interpreted as (-1 * $a). > > As you mention this, for completeness: +$a is interpreted as (1 * $a). > > It may be worthwhile to give a more explicit list for a) operators > that can be indirectly overloaded (and their desugaring) and b) > operators that cannot be overloaded (like the boolean operators, > comparison operators, instanceof, probably others). > > > If the operator handler function declares typehints (e.g. public > static function __add(Vector3 $lhs, int $rhs)), the function handler > is not called if the operand types do not match the signature, and the > other operand's handler is tried to call. > > I'm somewhat skeptical about this. This smells of method overloading, > and we don't do method overloading. There is no other place in PHP > that would perform dispatching based on the method signature, even if > in this case it's not a choice between multiple methods on the same > class, but rather multiple methods on different classes. Some of the > usual problems of method overloading don't apply here (in particular, > one could make a reasonable argument that the method on the first > object should be chosen, even if there is a more specific signature > available on the second object), but I'm skeptical about introducing > this kind of special case in the language specification. > > Regarding interfaces: I pretty strongly think that using interfaces > for this purpose is not a good idea. Interfaces are about contracts, > and there is no way (within the current limitations of PHP's type > system) to define a useful contract here. > > I think others have already expanded on why it's not possible to group > operators in a meaningful way. The DateTime example is probably the > most pertinent there, in that DateTime + DateTime is illegal, while > DateTime - DateTime is legal. If we can't even require both + and - as > part of one interface, there is very little we can require. > > However, even if we split each method into it's own interface, what > we'll be left with is something like > > interface Add { >     public static function add($a, $b); > } > > What does this interface tell us? Pretty much nothing. It tells us > that there probably exists at least one type with which this object > can be added, but not what that type actually is or what the result > type would be. There is no way to build code on the contract of "the > object is addable with *something*". > > To make this useful, the interface would actually have to look > something like this: > > interface Add<T1, T2, T3> { >     public static function add(T1 $a, T2 $b): T3; > } > > and then DateTime would implement something like this: > > class DateTime implements >     Sub<DateTime, DateTime, DateInterval>, >     Add<DateTime, DateInterval, DateTime> { ... } > > That's a contract you could build code on, though it would be rather > cumbersome. But in the absence of generic types, having an Add > interface is not useful. > > The way I see operator overloading being used in PHP, I would expect > code to be typed against specific classes, not supported operators. > The vast majority of code will be interested in accepting a Money, not > an Add. > > Regards, > Nikita
I have changed the names of the handlers like you suggested and added two tables which show what operators can be overloaded indirectly and which can not be overloaded at all. Thank you for your suggestion. I agree with your opinion with on interfaces, I think using magic methods is much more reasonable. Your point about the "smell of method overloading" is interesting. In my opinion this mechanism makes it a bit easier to use operator overloading as you dont have to do tedious typecheckings on your own in simple cases. But I agree that this behavior is a bit odd compared to other PHP features. That feature is not really needed for operator overloading, as you can use the PHP_UNKNOWN_OPERAND_TYPES const for signaling that the handler does not support the given types. If you (the internals developers) says that this does not fit into the concept of PHP, I will remove it from my RFC (or put it in a separate votation, if wished). Regards, Jan Böhmer

Nikita Popov

6 years ago
On Fri, Mar 6, 2020 at 9:17 AM Jan Böhmer <jan.h.boehmer@gmx.de> wrote:
> Am 02.03.2020 um 15:30 schrieb Nikita Popov: > > On Thu, Feb 27, 2020 at 9:43 PM <jan.h.boehmer@gmx.de> wrote: > >> On 15/02/2020 22:05, jan.h.boehmer@gmx.de wrote: >> > Hi internals, >> > >> > based on the discussions here (https://externals.io/message/108300) and >> here >> > (https://github.com/php/php-src/pull/5156), I have created a proper RFC >> for >> > userspace operator overloading: >> > https://wiki.php.net/rfc/userspace_operator_overloading >> > >> > The main differences to my original concept, is the removed __compare() >> > method (comparison overloading is a complex topic and should be handled >> in >> a >> > different RFC) and the possibility to signal that the operator handler >> does >> > not support the given types (by typehints or returning a special value). >> > This way, only one of both objects has to know about the other type. >> This >> > should expand the use case of operator overloading compared to my old >> > concept. >> > >> > What do you think about the RFC? >> > >> > Some discussion points, I can think of, would be the naming of the >> methods >> > (maybe naming them after the operator symbol and not the arithmetical >> > operation they represent, e.g. __plus instead of __add) or putting the >> > methods inside of interfaces like done for ArrayAccess (But I don’t see >> any >> > advantage in doing so, as it is very difficult grouping different >> operators >> > in a single interface usefully. Also, operators can accept and return >> > different types, so there is no real common interface between classes >> you >> > could rely on). >> > Furthermore, maybe the idea of allowing operator overloading in general >> > should be discussed as it is sometimes considered an anti-pattern (e.g. >> the >> > usage of '<<' for outputting a string in C++). On the other hand there >> are >> > many languages and libraries where operator overloading is used >> successfully >> > (e.g. numpy in Python). >> > >> > Regards, >> > Jan Böhmer >> >> I have changed the proposed names for the bitshift handlers to '__lshift' >> and '__rshift' (instead of __sl and __sr) to make more clear what operator >> is handled by the method (also the method names are now mostly consistent >> with the Python equivalents). >> >> How many of you would prefer a interface solution for operator >> overloading? >> I wonder if the RFC voting should include the option to choose between >> either the magic method approach (with the syntax proposed in the current >> RFC version) or using interfaces. >> For an interface version I would suggest these interfaces: >> ArithmeticOperators (implements +, -, *, /), PowOperator (**), >> ModuloOperator (%), ConcatOperator (.) and BitwiseOperators (~, &, |, ^, >> <<, >> >>). >> What would be appropriate names for the interface methods? If we just name >> them after the operation (like add()), it will become difficult to >> integrate >> these interfaces into existing code, as add() is already a used function >> name in many cases, but uses a different signature (non-static with one >> argument, whereas the interface needs a static one with two arguments). >> >> Regards, >> Jan >> > > Some notes: > > Naming: If we're already going for more verbose names, I'd prefer > __shiftLeft over __lshift. It may also make sense to use __bitwiseNot > rather than __not, as it's currently not obviously whether it overloads the > ~ or the ! operator. Similarly __bitwiseAnd could be better than __and, to > make it clear that this is about & and not && or "and". Same for the other > bitwise operators. > > > -$a is interpreted as (-1 * $a). > > As you mention this, for completeness: +$a is interpreted as (1 * $a). > > It may be worthwhile to give a more explicit list for a) operators that > can be indirectly overloaded (and their desugaring) and b) operators that > cannot be overloaded (like the boolean operators, comparison operators, > instanceof, probably others). > > > If the operator handler function declares typehints (e.g. public static > function __add(Vector3 $lhs, int $rhs)), the function handler is not called > if the operand types do not match the signature, and the other operand's > handler is tried to call. > > I'm somewhat skeptical about this. This smells of method overloading, and > we don't do method overloading. There is no other place in PHP that would > perform dispatching based on the method signature, even if in this case > it's not a choice between multiple methods on the same class, but rather > multiple methods on different classes. Some of the usual problems of method > overloading don't apply here (in particular, one could make a reasonable > argument that the method on the first object should be chosen, even if > there is a more specific signature available on the second object), but I'm > skeptical about introducing this kind of special case in the language > specification. > > Regarding interfaces: I pretty strongly think that using interfaces for > this purpose is not a good idea. Interfaces are about contracts, and there > is no way (within the current limitations of PHP's type system) to define a > useful contract here. > > I think others have already expanded on why it's not possible to group > operators in a meaningful way. The DateTime example is probably the most > pertinent there, in that DateTime + DateTime is illegal, while DateTime - > DateTime is legal. If we can't even require both + and - as part of one > interface, there is very little we can require. > > However, even if we split each method into it's own interface, what we'll > be left with is something like > > interface Add { > public static function add($a, $b); > } > > What does this interface tell us? Pretty much nothing. It tells us that > there probably exists at least one type with which this object can be > added, but not what that type actually is or what the result type would be. > There is no way to build code on the contract of "the object is addable > with *something*". > > To make this useful, the interface would actually have to look something > like this: > > interface Add<T1, T2, T3> { > public static function add(T1 $a, T2 $b): T3; > } > > and then DateTime would implement something like this: > > class DateTime implements > Sub<DateTime, DateTime, DateInterval>, > Add<DateTime, DateInterval, DateTime> { ... } > > That's a contract you could build code on, though it would be rather > cumbersome. But in the absence of generic types, having an Add interface is > not useful. > > The way I see operator overloading being used in PHP, I would expect code > to be typed against specific classes, not supported operators. The vast > majority of code will be interested in accepting a Money, not an Add. > > Regards, > Nikita > > I have changed the names of the handlers like you suggested and added two > tables which show what operators can be overloaded indirectly and which can > not be overloaded at all. Thank you for your suggestion. > > I agree with your opinion with on interfaces, I think using magic methods > is much more reasonable. > > Your point about the "smell of method overloading" is interesting. In my > opinion this mechanism makes it a bit easier to use operator overloading as > you dont have to do tedious typecheckings on your own in simple cases. But > I agree that this behavior is a bit odd compared to other PHP features. > > That feature is not really needed for operator overloading, as you can use > the PHP_UNKNOWN_OPERAND_TYPES const for signaling that the handler does not > support the given types. If you (the internals developers) says that this > does not fit into the concept of PHP, I will remove it from my RFC (or put > it in a separate votation, if wished). >
Does anyone else have thoughts on the ability to specify the supported types in the signature? Nikita

Christoph Becker

6 years ago
On 11.03.2020 at 10:22, Nikita Popov wrote:
> On Fri, Mar 6, 2020 at 9:17 AM Jan Böhmer <jan.h.boehmer@gmx.de> wrote: > >> Am 02.03.2020 um 15:30 schrieb Nikita Popov: >> >>> On Thu, Feb 27, 2020 at 9:43 PM <jan.h.boehmer@gmx.de> wrote: >>> >>>> If the operator handler function declares typehints (e.g. public static >>>> function __add(Vector3 $lhs, int $rhs)), the function handler is not called >>>> if the operand types do not match the signature, and the other operand's >>>> handler is tried to call. >>> >>> I'm somewhat skeptical about this. This smells of method overloading, and >>> we don't do method overloading. There is no other place in PHP that would >>> perform dispatching based on the method signature, even if in this case >>> it's not a choice between multiple methods on the same class, but rather >>> multiple methods on different classes. Some of the usual problems of method >>> overloading don't apply here (in particular, one could make a reasonable >>> argument that the method on the first object should be chosen, even if >>> there is a more specific signature available on the second object), but I'm >>> skeptical about introducing this kind of special case in the language >>> specification. >> >> Your point about the "smell of method overloading" is interesting. In my >> opinion this mechanism makes it a bit easier to use operator overloading as >> you dont have to do tedious typecheckings on your own in simple cases. But >> I agree that this behavior is a bit odd compared to other PHP features. >> >> That feature is not really needed for operator overloading, as you can use >> the PHP_UNKNOWN_OPERAND_TYPES const for signaling that the handler does not >> support the given types. If you (the internals developers) says that this >> does not fit into the concept of PHP, I will remove it from my RFC (or put >> it in a separate votation, if wished). > > Does anyone else have thoughts on the ability to specify the supported > types in the signature?
I agree that we should not introduce (this special case of) overloaded functions. Thanks, Christoph

Unnamed Person

6 years ago
On 11.03.2020 at 10:50, Christoph M. Becker wrote:
> On 11.03.2020 at 10:22, Nikita Popov wrote: > >> On Fri, Mar 6, 2020 at 9:17 AM Jan Böhmer <jan.h.boehmer@gmx.de> wrote: >> >>> Am 02.03.2020 um 15:30 schrieb Nikita Popov: >>> >>>> On Thu, Feb 27, 2020 at 9:43 PM <jan.h.boehmer@gmx.de> wrote: >>>> >>>>> If the operator handler function declares typehints (e.g. public static >>>>> function __add(Vector3 $lhs, int $rhs)), the function handler is not called >>>>> if the operand types do not match the signature, and the other operand's >>>>> handler is tried to call. >>>> I'm somewhat skeptical about this. This smells of method overloading, and >>>> we don't do method overloading. There is no other place in PHP that would >>>> perform dispatching based on the method signature, even if in this case >>>> it's not a choice between multiple methods on the same class, but rather >>>> multiple methods on different classes. Some of the usual problems of method >>>> overloading don't apply here (in particular, one could make a reasonable >>>> argument that the method on the first object should be chosen, even if >>>> there is a more specific signature available on the second object), but I'm >>>> skeptical about introducing this kind of special case in the language >>>> specification. >>> Your point about the "smell of method overloading" is interesting. In my >>> opinion this mechanism makes it a bit easier to use operator overloading as >>> you dont have to do tedious typecheckings on your own in simple cases. But >>> I agree that this behavior is a bit odd compared to other PHP features. >>> >>> That feature is not really needed for operator overloading, as you can use >>> the PHP_UNKNOWN_OPERAND_TYPES const for signaling that the handler does not >>> support the given types. If you (the internals developers) says that this >>> does not fit into the concept of PHP, I will remove it from my RFC (or put >>> it in a separate votation, if wished). >> Does anyone else have thoughts on the ability to specify the supported >> types in the signature? > I agree that we should not introduce (this special case of) overloaded > functions. > > Thanks, > Christoph
I have thought about this the last days, and I agree with you. Therefore I have removed this feature from the RFC. If PHP perhaps gets real function overloading someday, we could consider then to integrate operator overloading like in other languages (a separate handler for each type combination), which would be much more useful than the mechanism I had proposed. If there are no other open discussion points, I would put the RFC to voting in the next days. Thank you for your feedback, Jan

Rowan Collins

6 years ago
On Thu, 12 Mar 2020 at 08:31, Jan Böhmer <jan.h.boehmer@gmx.de> wrote:
> On 11.03.2020 at 10:50, Christoph M. Becker wrote: > > On 11.03.2020 at 10:22, Nikita Popov wrote: > >> Does anyone else have thoughts on the ability to specify the supported > >> types in the signature? > > I agree that we should not introduce (this special case of) overloaded > > functions. > > I have thought about this the last days, and I agree with you. Therefore > I have removed this feature from the RFC. >
I've been thinking about it as well, and am not sure what happens _without_ it in a case like this: # initial class, only overloads + on instances of itself class A { public static function __add(A $lhs, A $rhs) { // ... } } # in some third-party library class B { public static function __add(A|B $lhs, A|B $rhs) { // ... } } $a = new A; $b = new B; var_dump($b + $a); # calls B::__add($b, $a); OK var_dump($a + $b); # calls A::__add($a, $b), which is a TypeError If the engine doesn't catch the TypeError, it will never try B::__add($a, $b) in the second case, so presumably the TypeError will just be thrown to the user, which doesn't seem very helpful. Perhaps the simplest solution is to have a restriction that the operator overloads _must not_ specify types in their signature? Regards,
-- Rowan Tommins [IMSoP]

Unnamed Person

6 years ago
On Thu, 12 Mar 2020 at 10:06, rowan.collins@gmail.com wrote:
> On Thu, 12 Mar 2020 at 08:31, Jan Böhmer <jan.h.boehmer@gmx.de> wrote: > >> On 11.03.2020 at 10:50, Christoph M. Becker wrote: >> > On 11.03.2020 at 10:22, Nikita Popov wrote: >> >> Does anyone else have thoughts on the ability to specify the supported >> >> types in the signature? >> > I agree that we should not introduce (this special case of) overloaded >> > functions. >> >> I have thought about this the last days, and I agree with you. Therefore >> I have removed this feature from the RFC. >> > > > I've been thinking about it as well, and am not sure what happens _without_ > it in a case like this: > > # initial class, only overloads + on instances of itself > class A { > public static function __add(A $lhs, A $rhs) { > // ... > } > } > > # in some third-party library > class B { > public static function __add(A|B $lhs, A|B $rhs) { > // ... > } > } > > $a =3D new A; > $b =3D new B; > var_dump($b + $a); # calls B::__add($b, $a); OK > var_dump($a + $b); # calls A::__add($a, $b), which is a TypeError > > If the engine doesn't catch the TypeError, it will never try B::__add($a, > $b) in the second case, so presumably the TypeError will just be thrown to > the user, which doesn't seem very helpful. > > Perhaps the simplest solution is to have a restriction that the operator > overloads _must not_ specify types in their signature? > > Regards, > > Rowan Tommins
I have implmented your suggestion. If an operator handler has typehints, an error will be thrown, similar to the behavior if they would not have the required amount of arguments or were not static. The restriction that some functions can not declare typehints is not completly new, e.g. constructor must not declare a RETURN typehint, so I think it is not completly inappropriate to prevent your mentioned problems this way and I will most likely add this to the RFC. What do others think about this restriction? Furthermore I added a check that no arguments are passed by reference (like with other magic methods). We can not really prevent that a user manipulates passed objects (as they are always references), but at least for builtin values (int, string, etc.) it should not possible to modify their source. Also this would lead to problems, as the 1 in something like $a + 1 can not be passed as reference. Regards, Jan Böhmer

Terje Slettebø

6 years ago
> On 15/02/2020 22:05, jan.h.boehmer@gmx.de wrote: > > How many of you would prefer a interface solution for operator > overloading? > I wonder if the RFC voting should include the option to choose between > either the magic method approach (with the syntax proposed in the > current > RFC version) or using interfaces. > For an interface version I would suggest these interfaces: > ArithmeticOperators (implements +, -, *, /), PowOperator (**), > ModuloOperator (%), ConcatOperator (.) and BitwiseOperators (~, &, |, > ^, <<, >>> ).
+1 for magic methods. There's too many domains where only a few of the operators makes sense and groupings like ArithmeticOperators would not make sense, not at least one of the motivating examples of this proposal: Being able to write a Money class and associated functionality. Adding and subtracting money makes sense, but it makes no sense to multiply or divide them, and this is just *one* domain. Please, let's not go there and artificially limit the possibilities of future developers. For example, for me, it's quite natural to be able to write code like this: $total_discount = $total * $discount; Here, $total_discount and $total is Money, while $discount is a Percentage, so we should be able to define a method that allows you to multiply Money with Percentage, and return a Money object. Adding Money and Percentage would make no sense, but multiplying them does. Regards, Terje

Rowan Collins

6 years ago
Hi Terje, I think both of your examples are compatible with the idea of grouped operators, as long as we don't constrain a type to implement all operators with the same right-hand side. On Mon, 2 Mar 2020 at 17:23, Terje Slettebø < terje.slettebo@sandefjordbredband.net> wrote:
> > Adding and subtracting money makes sense, but it makes no sense to > multiply > or divide them, and this is just *one* domain. >
This is true, but it does make sense to multiply and divide them by numbers, for instance $totalPrice = $unitPrice * $quantity In general, if $a + $a represents addition, then $a * 2 should logically give the same result. As has been raised, there is an argument for keeping / separate, for types that cannot handle fractional operations; it would be a matter of choice whether $price / 2 should be supported because it would lead to fractional pennies/cents.
> $total_discount = $total * $discount; > > Here, $total_discount and $total is Money, while $discount is a > Percentage, > so we should be able to define a method that allows you to multiply Money > with Percentage, and return a Money object. Adding Money and Percentage > would > make no sense, but multiplying them does. >
If this operator was implemented in isolation, it would again be drifting into domain-specific language territory. (I don't personally have a problem with that, but many people vocally object to it.) However, it could easily be made part of a consistent set of arithmetic operators: On class Money: Money + Money => Money Money - Money => Money Money * int => Money Money / int => Money int * Money => Money int / Money => Error Money * Percentage => Money Money / Percentage => Money Percentage * Money => Money Percentage / Money => Error Optionally, on class Percentage: Percentage + Percentage => Percentage Percentage - Percentage => Percentage Percentage* int => Percentage Percentage / int => Percentage Regards,
-- Rowan Tommins [IMSoP]

Terje Slettebø

6 years ago
Hi Rowan. I agree that this makes sense. However, as someone else pointed out, one problem with interfaces is that they constrain the types that may be used. We have our own Money, Percentage, PricePerMm, etc. types that we'd like to define operators for, how can you define an interface in PHP that accommodate types that are unknown to the PHP implementors? If we had support for generics, we could have defined generic interfaces, but we don't so we can't. Regards, Terje

Rowan Collins

6 years ago
On Tue, 3 Mar 2020 at 14:45, Terje Slettebø < terje.slettebo@sandefjordbredband.net> wrote:
> > I agree that this makes sense. However, as someone else pointed out, one > problem with interfaces is that they constrain the types that may be used. >
An interface doesn't have to define types, and in this case it wouldn't make sense for it to do so. Without generics, the only option would be to constrain everything to "static", but as you say, that rules out a lot of use cases. So the interfaces would simply require that the functions exist, and not say anything about their types. This is already the case with other built-in interfaces; for instance, Iterator places no constraint on the return of the current() and key() functions: # https://3v4l.org/U0gKD class Foo implements Iterator { public function current(): Foo { return $this; } public function key(): Foo { return $this; } public function next(): void {} public function rewind(): void {} public function valid(): bool { return true; } } Similarly, the interface can't guarantee that the implementation does anything useful, or even runs without error. All it does is force the user to at least consider Regards,
-- Rowan Tommins [IMSoP]