Patricio Tarantino has asked me to help him propose Operator
Overloading in PHP 7.1 (based in part on my operator extension in
PECL). I think we can expose this to usespace as magic methods with
very little overhead (the runtime check and dispatch is already there,
after all).
I do think that the "Future Expansion" section bears following through
with as well, but the basic set of methods already hooked for GMP
would be a nice start.
https://wiki.php.net/rfc/operator-overloading
-Sara
I would love to see all of the is_* methods implemented in the initial
implementation, these are far more useful to me than the basic math
operators.
As for interfaces, I'm generally pro-interfaces as programmatic
documentation if nothing else — however, none of the magic methods have
implemented this except for the JsonSerializable stuff. But, I'm all for
moving the language forward on new features only and not getting caught in
the "legacy consistency" trap again.
We can maybe add optional interfaces for other magic methods, and then make
them required in the next major.
I've been hoping for operator overloading in userland since the GMP changes
and welcome this proposal in general.
Thanks Patricio and Sara both! :)
Patricio Tarantino has asked me to help him propose Operator
Overloading in PHP 7.1 (based in part on my operator extension in
PECL). I think we can expose this to usespace as magic methods with
very little overhead (the runtime check and dispatch is already there,
after all).I do think that the "Future Expansion" section bears following through
with as well, but the basic set of methods already hooked for GMP
would be a nice start.https://wiki.php.net/rfc/operator-overloading
-Sara
Hi Sara and Patricio,
Patricio Tarantino has asked me to help him propose Operator
Overloading in PHP 7.1 (based in part on my operator extension in
PECL). I think we can expose this to usespace as magic methods with
very little overhead (the runtime check and dispatch is already there,
after all).
+1 to moving this out of PECL and into the engine.
I do think that the "Future Expansion" section bears following through
with as well, but the basic set of methods already hooked for GMP
would be a nice start.
Whoa, that's a big list of magic methods. I wonder if a generic dispatch
magic method would be more developer friendly. Something like:
public function __overload(string $operator, object $rhs = null); // $rhs
=== null iff op is unary
Thanks!
bishop
Hi!
Patricio Tarantino has asked me to help him propose Operator
Overloading in PHP 7.1 (based in part on my operator extension in
PECL). I think we can expose this to usespace as magic methods with
very little overhead (the runtime check and dispatch is already there,
after all).
We could, but I'm not sure whether we should. One of the reasons why
userspace operator overloading was kept out was that a lot of its uses
make code worse, not better. When you see expression that looks like
addition but you know it can do literally anything, that makes reading
the code so much more frustrating. I mean, you can hope * still means
"multiply", but you can't know it anymore. And there are not that many
types for which * has natural meaning.
Additionally, introducing about 9000^W^W a lot of new magics drastically
raises both cognitive complexity of the language (now we have 14 magics,
adding operators (and RFC lists only arithmetic ones, though one may say
why stop there - C++ certainly didn't) more than triples that count.
Also, implementing them in consistent fashion would be non-trivial, and
imagine what fun would be had when +, += and ++ work differently (btw,
why have different implementations for + and += at all?). And then we
get questions of commutativity and associativity...
What I'm trying to say, doing proper operators set is not that trivial,
and besides a very small set of use cases, I'm concerned it would be
used to write very clever write-once read-never code instead of good code.
--
Stas Malyshev
smalyshev@gmail.com
Patricio Tarantino has asked me to help him propose Operator
Overloading in PHP 7.1 (based in part on my operator extension in
PECL). I think we can expose this to usespace as magic methods with
very little overhead (the runtime check and dispatch is already there,
after all).We could, but I'm not sure whether we should. One of the reasons why
userspace operator overloading was kept out was that a lot of its uses
make code worse, not better. When you see expression that looks like
addition but you know it can do literally anything, that makes reading
the code so much more frustrating. I mean, you can hope * still means
"multiply", but you can't know it anymore. And there are not that many
types for which * has natural meaning.
I can't argue with you there. Magic can be exceedingly dangerous in
the wrong hands. I think the core question of this RFC is: Is the
responsibility to use magic safely something to grant PHP developers.
I'm personally not decided on it, which is why operator stayed a PECL
extension and it's taken someone else to put the RFC forward (even if
I am sponsoring him to get the discussion rolling).
Additionally, introducing about 9000^W^W a lot of new magics drastically
raises both cognitive complexity of the language (now we have 14 magics,
adding operators (and RFC lists only arithmetic ones, though one may say
why stop there - C++ certainly didn't) more than triples that count.
I think this can be handled by taking Bishop's suggestion. After all,
it's how it's actually implemented internally via do_operation. We
could apply the same to the userspace callback and simplify the
implementation details, even if it add slight complexity to script
implementations.
Also, implementing them in consistent fashion would be non-trivial, and
imagine what fun would be had when +, += and ++ work differently
Hilarious to hear you cite that as the already existing inconsistency
between += and ++ with non-numeric strings came up only last week.
$x = 'foo';
$x++; // $x === 'fop'
$x += 1; // $x === 1
But I digress, your point is taken, and it comes back to the same
thing in my first paragraph response. I think we can all agree that
the potential for evil is great, the question is whether or not we
trust developers with great power.
(btw, why have different implementations for + and += at all?).
Because the former would typically result in a new object, the latter
wouldn't. In the case of GMP it's the difference between:
$x = new GMP(1);
$x = $x + 1; // Makes a new GMP instance and replaces $x
$x = new GMP(1);
$x += 1; // Updates the internal value of the GMP instance
That's a significant difference in terms of semantics if there are
other references to $x lying around. Nevermind the cost of
newing/cloning/destroying objects when all you really wanna do is
mutate an existing one a bit.
And then we get questions of commutativity and associativity...
Yeah, this was always one of my annoyances of pecl/operator.
Honestly, I could see making them always-left-associative to avoid
some unforseen wtf. But (if combined with the additional operations)
would require resolving the commutativity that happens in
ZEND_AST_GREATER* (not terribly complex to handle, but we'd have to
decide to do it).
What I'm trying to say, doing proper operators set is not that trivial,
and besides a very small set of use cases, I'm concerned it would be
used to write very clever write-once read-never code instead of good code.
Not arguing with that at all. :)
-Sara
Hi!
Hilarious to hear you cite that as the already existing inconsistency
between += and ++ with non-numeric strings came up only last week.$x = 'foo';
$x++; // $x === 'fop'
$x += 1; // $x === 1
Exactly, because on strings ++ and += 1 are not the same operator. And,
as you noted, it requires a pause to figure it out. But now imagine
somebody overrides + and ++ to work on files, doing whatever sounded
clever at the moment - how easy would it be to read that code? To
maintain it? That's my concern.
I do agree that is a question of judgement if we want this kind of
magic. Right now I am leaning to the "no" side, especially given the RFC
lacks the description of the actual use cases. I mean, the coolness
factor is there, all the cool boys - C++, Python, Swift, Scala - do
that, but I'd like to see what are the use cases specifically for PHP.
That's a significant difference in terms of semantics if there are
other references to $x lying around. Nevermind the cost of
newing/cloning/destroying objects when all you really wanna do is
mutate an existing one a bit.
Right. But properly covering all this would require either a lot of
boilerplate code or some very tricky engine coding. BTW, I don't think
for GMP right now += modifies existing object. I guess it may be
possible to implement it the other way (not sure).
Stas Malyshev
smalyshev@gmail.com
BTW, I don't think for GMP right now += modifies existing object.
I guess it may be possible to implement it the other way (not sure).
It doesn't. As seen below, we get a new, second object on the
assign_add. Wrong behavior IMO, but something to fix separately from
this.
$g = gmp_init(123);
var_dump($g); // object(GMP)#1 { ["num"]=> 123 }
$g += 1;
var_dump($g); // object(GMP)#2 { ["num"]=> 124 }
-Sara
Hi Sara,
Patricio Tarantino has asked me to help him propose Operator
Overloading in PHP 7.1 (based in part on my operator extension in
PECL). I think we can expose this to usespace as magic methods with
very little overhead (the runtime check and dispatch is already there,
after all).I do think that the "Future Expansion" section bears following through
with as well, but the basic set of methods already hooked for GMP
would be a nice start.https://wiki.php.net/rfc/operator-overloading
-Sara
While I don’t necessarily think adding operator overloading is a great idea, I think if it’s to be done, we could do better. I’d offer two suggestions:
- rather than the list of magic function names, use the actual operators themselves
function ^($value)
or
function operator <<($value)
This way, new magic methods don’t have to be created in the event we created new operators later. (Unlikely, I know.)
To handle ambiguity between prefix/postfix/infix operators, you could use something like:
function prefix -($value)
function infix -($left, $right)
function postfix ++($value)
This would also address an issue in the proposal as written: __add and __sub don’t handle the ambiguity of prefix + and - operators.
Bishop Bettini suggests:
public function __overload(string $operator, object $rhs = null); // $rhs === null iff op is unary
but you really need separate functions (or an additional parameter) to handle that case. You can’t only pass in null because then you can’t disambiguate between $foo - null and -$foo.
- I think we should take a look at Swift’s operator definition mechanism. Besides just allowing for operator overloading, Swift also allows you to specify entirely custom operators. For example (from the Swift Programming Language book, Language Guide, Advanced Operators chapter):
“New operators are declared at a global level using the operator keyword, and are marked with the prefix, infix or postfix modifiers:
prefix operator +++ {}
The example above defines a new prefix operator called +++. This operator does not have an existing meaning in Swift, and so it is given its own custom meaning below in the specific context of working with Vector2D instances. For the purposes of this example, +++ is treated as a new “prefix doubling incrementer” operator. It doubles the x and y values of a Vector2D instance, by adding the vector to itself with the addition assignment operator defined earlier:
prefix func +++ (inout vector: Vector2D) -> Vector2D {
vector += vector
return vector
}”
“The following example defines a new custom infix operator called +-, with left associativity and a precedence of 140:”
“infix operator +- { associativity left precedence 140 }
func +- (left: Vector2D, right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x, y: left.y - right.y)
}”
Operators can be defined as prefix, infix, or postfix. Infix operators can specify associativity (left, right, or none (default)) and precedence. Swift limits the first character of an operator is limited to one from a set of typical operator symbols and a set of unicode characters, so you can’t create or overload operators using arbitrary ascii characters.
In PHP, we might imagine this example to look like:
//in global scope
operator infix +- associativity(left) precedence(140);
function +-($left, $right) { … }
In Swift, operator definitions and overloads are done in on a global basis (and multiple definitions are resolved appropriately with Swift’s type system). This makes sense for Swift, but it would be more difficult for PHP, given PHP’s more flexible type system. I’m unclear what the best approach is to handle typehints, which in this case are more part of the operator functions logic name than just run-time limitations on parameters.
The major problem I see with this, besides the parser complexity it would entail, is that it would make static analysis that much harder, and you wind up with PHP code that can’t actually compile unless the operator definitions are loaded.
-John
Patricio Tarantino has asked me to help him propose Operator
Overloading in PHP 7.1 (based in part on my operator extension in
PECL). I think we can expose this to usespace as magic methods with
very little overhead (the runtime check and dispatch is already there,
after all).I do think that the "Future Expansion" section bears following through
with as well, but the basic set of methods already hooked for GMP
would be a nice start.
My ISP is having issues with no resolution in sight and I can't view
the RFC right now. So sorry for asking a question that is probably
answered in the RFC: this proposes using magic methods, not
interfaces, correct? If so, what is the justification?
Patricio Tarantino has asked me to help him propose Operator
Overloading in PHP 7.1 (based in part on my operator extension in
PECL). I think we can expose this to usespace as magic methods with
very little overhead (the runtime check and dispatch is already there,
after all).I do think that the "Future Expansion" section bears following through
with as well, but the basic set of methods already hooked for GMP
would be a nice start.https://wiki.php.net/rfc/operator-overloading
-Sara
Thanks for the proposal, Sara! A few questions to clarify:
-
If an object implements add, will $a += $b be equivalent to $a =
$a->__add($b) and ++$a be equivalent to $a = $a->__add(1)? This is how
internal operator overloading currently works (we have no separate
overloads for assign ops). Will this RFC also work this way? -
Regarding associativity, the RFC states "When both operands to a binary
expression implement operator overloading, the left-hand operand's handler
will be invoked. If only one of the two implement overloading, then that
side is invoked with the relative operation inverted as necessary." This
sounds rather fishy to me, as not all operations are abelian. In particular
__sub, __div, __pow and __concat are usually non-commutative. As such,
the current RFC does not appear to leave room for supporting things like
1/$complex or 1-$complex or e**$complex. As such I wonder if these magic
methods oughtn't be static methods with two operands. -
Relatedly, consider the situation where $obj1 op $obj2, where $obj1 and
$obj2 are instances of different classes, both of which have distinct
overloads of the op operator. It so happens that the op-implementation of
$obj1 does not support operations against $obj2, while the
op-implementation on $obj2 does support operations against $obj1. The
op-implementation of $obj1 will be invoked first -- will there be some way
for it to signal "I don't support this operation", thus allowing the
op-implementation of $obj2 to be used?
Thanks,
Nikita
Thanks for the proposal, Sara! A few questions to clarify:
One point of order. It's Patricio's peoposal, I'm just helping him move it along.
- If an object implements add, will $a += $b be equivalent to $a = $a->__add($b) and ++$a be equivalent to $a = $a->__add(1)? This is how internal operator overloading currently works (we have no separate overloads for assign ops). Will this RFC also work this way?
To be quite honest, I'd rather it DIDN'T work that way. As I said to Stas, I regard our current crossloading as broken since it forces new object creation when the statement visually calls for mutation.
Semantically, what we do for GMP right now isn't sound, even if the distinction doesn't seem to be breaking anyone.
- Regarding associativity, the RFC states "When both operands to a binary expression implement operator overloading, the left-hand operand's handler will be invoked. If only one of the two implement overloading, then that side is invoked with the relative operation inverted as necessary." This sounds rather fishy to me, as not all operations are abelian. In particular __sub, __div, __pow and __concat are usually non-commutative. As such, the current RFC does not appear to leave room for supporting things like 1/$complex or 1-$complex or e**$complex. As such I wonder if these magic methods oughtn't be static methods with two operands.
I did consider that as an alternate option.
function overload_register($op, $lhtype, $rhtype, $callback);
overload_type(ZEND_ADD, 'Complex', 'any', 'Complex::add');
This would get pretty expensive in terms of large lookup tables though, so I'm hesitant there.
- Relatedly, consider the situation where $obj1 op $obj2, where $obj1 and $obj2 are instances of different classes, both of which have distinct overloads of the op operator. It so happens that the op-implementation of $obj1 does not support operations against $obj2, while the op-implementation on $obj2 does support operations against $obj1. The op-implementation of $obj1 will be invoked first -- will there be some way for it to signal "I don't support this operation", thus allowing the op-implementation of $obj2 to be used?
I do like the idea of being able to signal type failures. Would a specific exception be too heavyweight? Or just return "ConversionFailure" object instance or something...
-Sara
Patricio Tarantino has asked me to help him propose Operator
Overloading in PHP 7.1 (based in part on my operator extension in
PECL). I think we can expose this to usespace as magic methods with
very little overhead (the runtime check and dispatch is already there,
after all).https://wiki.php.net/rfc/operator-overloading
It's something I'd love to see, and would be particularly useful for my
Complex Number and Matrix libraries, but (as Bishop says) the large list
of magic methods does feel excessive, and a single dispatcher method
feels simpler.
But how then would it be possible to overload several operators? If the
__overload() dispatcher method has to implement a switch on the value of
the operator argument, and implement error handling for invalid or
non-implemented operators, it's pushing a lot of complexity into that
magic method
--
Mark Baker
|. \ -3
|J/ PHP |
|| | __ |
|| |m| |m|
I LOVE PHP
Patricio Tarantino has asked me to help him propose Operator
Overloading in PHP 7.1 (based in part on my operator extension in
PECL). I think we can expose this to usespace as magic methods with
very little overhead (the runtime check and dispatch is already there,
after all).I do think that the "Future Expansion" section bears following through
with as well, but the basic set of methods already hooked for GMP
would be a nice start.https://wiki.php.net/rfc/operator-overloading
-Sara
I'm really torn here.
First off, IF PHP were to adopt such overloading, I would much prefer
special interfaces over magic methods. It's simply far more
self-documenting and reduces the difference between user-space and
PHP-space features. (And I'd be all for migrating the current magic
methods to that, too, if we wanted consistency.)
That said... as Stas notes the can of worms this entails is quite
large. I've started thinking more in terms of pure value objects in the
past year (PSR-7-inspired), and for those, having richer operations can
be quite useful. DateTime, for instance, supports > and <, which would
be similarly helpful on other value objects. Think money for a good
example, or object-based collections. It also serves to improve the
type system, as a Currency object could allow itself to be added iff the
other Currency object was in the same denomination.
That said, the potential for magic "WTF were you thinking?" there is
also scarily large. Stas hinted at an example, as does the RFC itself.
From the first example:
public function __add($rhs) {
$ret = clone $this;
if ($rhs instanceof Complex) {
$ret->real += $rhs->real;
$ret->imaginary += $rhs->imaginary;
} else {
$ret->real += (int)$rhs;
}
return $ret;
}
That all seems reasonable. Return a new object that is the result of
adding the real and imaginary components together. Cool. I give it
about 8 minutes before someone forgets to clone $this first and just
starts returning $this, in which case you now have an addition operator
that both modifies a value in place and returns itself. I then give it
about 4 minutes after that before someone tries to defend that as a good
practice, and now we have a large cadre of people who will insist that
$c = $a + $b;
modifying $a along the way is "totally a legit thing, it's a performance
optimization, stop being so pedantic telling me I shouldn't do that!"
(Come on, you know that's going to happen.) The worst-case scenario
there is not guaranteed, but it still scares the bejeebus out of me.
So for the moment, I'm tentatively -1. Perhaps limiting it for now to
the obviously immutable operators, ie the comparison operators only,
would be a safe babystep?
--Larry Garfield
Patricio Tarantino has asked me to help him propose Operator
Overloading in PHP 7.1 (based in part on my operator extension in
PECL). I think we can expose this to usespace as magic methods with
very little overhead (the runtime check and dispatch is already there,
after all).I do think that the "Future Expansion" section bears following through
with as well, but the basic set of methods already hooked for GMP
would be a nice start.
"Limiting Issue: Greater/Lesser are not distinct"
That should be addressed in the engine first too (or with) this change.
However, operator overloading...
cheers,
Derick
Hi Sara,
Sara Golemon wrote:
Patricio Tarantino has asked me to help him propose Operator
Overloading in PHP 7.1 (based in part on my operator extension in
PECL). I think we can expose this to usespace as magic methods with
very little overhead (the runtime check and dispatch is already there,
after all).I do think that the "Future Expansion" section bears following through
with as well, but the basic set of methods already hooked for GMP
would be a nice start.
While I would like to see operator overloading in PHP, I don't
particularly like the approach this RFC takes. It seems to take the
approach C++, Python and so on use where you can simply overload any
operator as you see fit. While that is arguably useful, it is open to
abuse. In C++, for example, you can join two file paths by dividing
them, or write to a stream by bitwise shifting it left by another
stream. In Python, you repeat a string by finding the product of it and
an integer. Abusing operator overloading like this harms readability, is
not necessarily intuitive and is anyway unnecessary: a function or
method would work just as well in these situations. Also, I'm not sure
it's a good fit for a dynamic language to make operators do different
things depending on their operand types. That just creates the
possibility of unpleasant surprises at runtime, and PHP has enough of
these already without the possibility of users creating more.
Luckily, C++ and Python's approach is not our only option. I am quite a
fan of Haskell's approach to operator overloading, which is less prone
to abuse. In Haskell, a purely-functional programming language, certain
operators (and also certain math functions like abs()
) are defined as
part of "typeclasses", somewhat akin to interfaces in classical
object-oriented languages like Java or PHP. These typeclasses group
related operations together, and a conforming implementation of that
typeclass must implement all the operations. For example, there is a
Num
typeclass[0] for numbers, which defines the addition, subtraction,
multiplication, negation, absolute value, sign and
conversion-from-integer operations. Extending Num
, there is a
Fractional
typeclass[1] for fractional number types, which adds
fractional division, reciprocal and conversion-from-rational operations.
There are likewise similar typeclasses for non-numbers, and other
descendents of Num
for other kinds of numbers. The approach Haskell
takes here is not as prone to abuse because you cannot simply implement
operations as you please: you must implement them as a set. (Though,
granted, Haskell also discourages overloading abuse by actually letting
you define custom operators.) With this approach Haskell also achieves
something else useful, in that you can use typeclasses as a type
constraint on function parameters and return types. I think it would be
more worth pursuing a Haskell-style approach in PHP, most likely with a
hierarchy of magic interfaces.
All that aside, I have some other issues with the RFC. The set of
overloadable operators proposed is very small, and I'm surprised it
doesn't even include the full set of number operations. But
additionally, if you were to fill in that set, I wonder if it might be
worthwhile making some of the math functions overloadable as well, since
otherwise you have an incomplete set of operators: %'s complement is
intdiv()
, not /, and the closest thing we have to a complement of / is
fmod()
. Likewise, since abs()
works on integers and floats, perhaps it
should work on all number types. I don't think GMP currently overloads
any of these, though, and there is the possibility we might create
confusion if overloading extended to math functions.
Another concern I have is the possibility of having assign*
overloads. These would useful for avoiding having to instantiate a new
object, but there's potential for them to be implemented wrongly, or be
implemented without their corresponding normal operations. Since we
don't have any way to make PHP 4-style copy-on-write value classes at
the moment, I think we should just do the simpler thing and assume we're
dealing with completely immutable objects, and therefore not have
assign* methods. Similarly, I don't think should have overloads for
++ and -- (the proposed /(post|pre)_(inc|dec)/ methods). Heck, they're
probably not even that useful, because you could simply check in __add
or __sub if the operand's absolute value is 1.
Regarding the possibility of comparison operators, again I think we
should go the simpler route. For <, <=, >, and >=, we only need one
overload method, perhaps __cmp, which returns the usual negative, zero
or positive value to indicate ordering. I can't see a benefit of having
a seperate methods for each of these, and it would introduce the risk of
them being implemented inconsistently, creating chaotic sorts. Similarly
for == and !=, we only need a single method, perhaps __is_equal, or
maybe we should not give them a separate method and group them with <,
<=, > and >= into __cmp, which would again have the benefit of
preventing inconsistent implementation. For === and !==, I would be wary
of letting them be overloaded, given they are currently "strict", and it
is useful to be able to check if two objects are the same instance,
something objects shouldn't need control over. == and !=, on the other
hand, compare content, which the objects themselves can probably decide
better than PHP's default behaviour.
Anyway, I appreciate you making this RFC, if only to prompt discussion.
Thanks.
[0] http://hackage.haskell.org/package/base-4.8.1.0/docs/Prelude.html#t:Num
[1]
http://hackage.haskell.org/package/base-4.8.1.0/docs/Prelude.html#t:Fractional
--
Andrea Faulds
http://ajf.me/
In Haskell, a purely-functional programming language, certain
operators (and also certain math functions likeabs()
) are defined as
part of "typeclasses", somewhat akin to interfaces in classical
object-oriented languages like Java or PHP. These typeclasses group
related operations together, and a conforming implementation of that
typeclass must implement all the operations. [...] I think it would be
more worth pursuing a Haskell-style approach in PHP, most likely with
a hierarchy of magic interfaces.
For those not familiar with type classes, it's worth mentioning
something you clarified to me over Twitter some time ago, about how they
differ from interfaces (correct me if I go wrong). With an interface,
the definition of GMP could include "implements NumLike", meaning that
any instance of GMP "is a NumLike object"; there are only two entities
to define: the interface, and the class which implements it. With a type
class, GMP itself wouldn't implement NumLike, you would have a third
entity, which was "an instance of NumLike for GMP objects"; GMP would
"have a NumLike type class", defined externally to it.
This goes some way to addressing the question of which operand receives
the message that an overloaded operator has been invoked - the
implementation would not have a "$this". As I understand it,
multi-parameter type classes even allow you to bridge between types that
aren't aware of each other - e.g. define the result of gmp_init(42) +
\Litipk\BigNumbers\Decimal::create(42)
Incidentally, my main exposure to operator overloading and custom
operators is in PostgreSQL, which implements them as a special case of
overloaded function despatch: you can have two functions "add(int, int)"
and "add(float, float)", which will be selected between when a statement
is compiled; you can then define the binary operator "+" for types "int,
int" maps to function "add(int, int)", and it's dispatched the same way.
Again, this removes the question of which operand should receive the
message, because the operator (and its associated function) are not part
of the type - if you want to add an int to a float, you can / must
define both "operator + (int, float)" and "operator + (float, int)".
Like in C++, though, Postgres's operators aren't intrinsically grouped,
and it's up to you to use them in idiomatic ways, such as "||" for
various types of concatenation (because SQL) and "@>" for "contains".
The major disadvantage to separating the implementation of the operator
/ type class from the class definition is that I can't see how it would
work with autoloading.
Regards,
--
Rowan Collins
[IMSoP]
Hi Rowan,
Rowan Collins wrote:
In Haskell, a purely-functional programming language, certain
operators (and also certain math functions likeabs()
) are defined as
part of "typeclasses", somewhat akin to interfaces in classical
object-oriented languages like Java or PHP. These typeclasses group
related operations together, and a conforming implementation of that
typeclass must implement all the operations. [...] I think it would be
more worth pursuing a Haskell-style approach in PHP, most likely with
a hierarchy of magic interfaces.For those not familiar with type classes, it's worth mentioning
something you clarified to me over Twitter some time ago, about how they
differ from interfaces (correct me if I go wrong). With an interface,
the definition of GMP could include "implements NumLike", meaning that
any instance of GMP "is a NumLike object"; there are only two entities
to define: the interface, and the class which implements it. With a type
class, GMP itself wouldn't implement NumLike, you would have a third
entity, which was "an instance of NumLike for GMP objects"; GMP would
"have a NumLike type class", defined externally to it.
That's correct, yes. In Haskell, the data type definition, the type
class definition, and the the type class implementation are all
separate, which is different from Java-style interfaces (which PHP has)
where you combine interface implementation with data type definition.
This goes some way to addressing the question of which operand receives
the message that an overloaded operator has been invoked - the
implementation would not have a "$this". As I understand it,
multi-parameter type classes even allow you to bridge between types that
aren't aware of each other - e.g. define the result of gmp_init(42) +
\Litipk\BigNumbers\Decimal::create(42)
Indeed, since type class implementations are separate from the data
types they apply to and the definition of the typeclass. Data types in
Haskell themselves don't really have awareness of other things anyway,
Haskell doesn't have objects.
(A nitpick: at least in the case of Num
type class, where + is
defined, Haskell doesn't actually let you define how to add one type to
another, at least not directly, since it has only one type parameter.
Instead, you have to do an explicit conversion if you want to do such an
addition. And anyway, it's true that for multi-parameter type classes,
what you describe would be possible.)
And yes, you can make an implementation of operators yourself for types
which don't already have one.
The major disadvantage to separating the implementation of the operator
/ type class from the class definition is that I can't see how it would
work with autoloading.
Yeah, I don't know how well type classes for operator overloading would
work in PHP. I was thinking of using interfaces instead for PHP operator
overloading, since they're what we already have and are familiar, even
if they lack some of type classes' benefits. The problem with
interfaces, though, is the $this one, as you mentioned. That's a bit of
a headache.
Thanks for your input!
Andrea Faulds
https://ajf.me/
Hi!
constraint on function parameters and return types. I think it would be
more worth pursuing a Haskell-style approach in PHP, most likely with a
hierarchy of magic interfaces.
I agree that interface approach looks better than Python/C++ approach
for PHP. One thing it also allows is introducing things in a gradual and
conceptually sound manner - i.e. Comparable, Numeric, etc. which would
implement specific operator set instead of just letting people
implementing random bag of operators. I'm not sure how to make it
technically but it looks better to me conceptually. We don't have as
powerful type system as Haskell but I think we still can do a lot with
interfaces.
should work on all number types. I don't think GMP currently overloads
any of these, though, and there is the possibility we might create
confusion if overloading extended to math functions.
Which reinforces the point above that maybe before the are tackling the
operators in userspace we need to identify use cases and create
structure for them, and then it might be easier for us to avoid
inconsistencies. We're kind of moved forward with GMP without doing
that, but fortunately it's easier to fix such things while it's confined
to the engine/extensions. Once it's in userspace, changing it would be
way harder.
--
Stas Malyshev
smalyshev@gmail.com
Hi Stas,
Stanislav Malyshev wrote:
Hi!
constraint on function parameters and return types. I think it would be
more worth pursuing a Haskell-style approach in PHP, most likely with a
hierarchy of magic interfaces.I agree that interface approach looks better than Python/C++ approach
for PHP. One thing it also allows is introducing things in a gradual and
conceptually sound manner - i.e. Comparable, Numeric, etc. which would
implement specific operator set instead of just letting people
implementing random bag of operators. I'm not sure how to make it
technically but it looks better to me conceptually. We don't have as
powerful type system as Haskell but I think we still can do a lot with
interfaces.
I agree that we could do something with interfaces. I would like to
point out that we actually already have an example of this, in the form
of the \ArrayAccess interface, which requires you to implement all the
different indexing operations at once. Unfortunately, though,
\ArrayAccess doesn't give us a precedent for dealing with the $this
issue (in $a + $b
, who gets called, how do we handle differing types,
etc?), but it's a start.
should work on all number types. I don't think GMP currently overloads
any of these, though, and there is the possibility we might create
confusion if overloading extended to math functions.Which reinforces the point above that maybe before the are tackling the
operators in userspace we need to identify use cases and create
structure for them, and then it might be easier for us to avoid
inconsistencies. We're kind of moved forward with GMP without doing
that, but fortunately it's easier to fix such things while it's confined
to the engine/extensions. Once it's in userspace, changing it would be
way harder.
In full agreement there. On that note, it's of course possible to
support overloading certain things, but only internally. It might be
reasonable to expose __assign_add (well, its C equivalent) to GMP but
not to userland, for instance.
Regarding use cases, there are a few that come to mind.
One of these is userland number types. We don't, to the best of my
knowledge, currently have a decimal type as a PHP extension, so
applications which need it must look to userland implementations (often
backed by bcmath underneath). These would be more pleasant and intuitive
to use if they could use our normal arithmetic operators. For such
userland types, I imagine an interface with basic arithmetic operations
would work. Perhaps we might call it \Number. A question might be
whether to split apart integer and float operations like Haskell does.
I'm not sure what fits this use-case best, though it's probably better
not to require classes to implement what they don't need, and just let
people get an error if they do, say, ($decimal % 5).
Another use case is userland string types. There's several projects out
there which wrap PHP strings in an object to provide an object-oriented
interface which is more pleasant to use than PHP's string API. There is
also the use-case of Unicode string wrappers, since PHP still doesn't
have a Unicode string type. They would benefit from being able to
overload the concatenation operator, and possibly also the binary
operations (^, & and |) which work on strings. Everything else these
types need is already overloadable with the existing \ArrayAccess and
\Iterator(Aggregate). There is a problem here, through, in what we
should require such classes to implement beyond concatenation. Requiring
nothing else leaves it open to abuse (I can already imagine .
being
used for the dot product), but requiring, say, binary operations would
be unfair (they're nonsense in the context of Unicode), as would
\ArrayAccess (Unicode strings may have non-constant time indexing) or
\Traversable (a Unicode string class might choose to lack a default
iterator to force the user to consider the different ways a Unicode
string can be used). Maybe we could require \Countable, but even that
has problems regarding Unicode, non-constant time complexity especially.
So, I'm not quite sure what to do there.
Thanks.
Andrea Faulds
https://ajf.me/
I agree that we could do something with interfaces. I would like to
point out that we actually already have an example of this, in the
form of the \ArrayAccess interface, which requires you to implement
all the different indexing operations at once. Unfortunately, though,
\ArrayAccess doesn't give us a precedent for dealing with the $this
issue (in$a + $b
, who gets called, how do we handle differing
types, etc?), but it's a start.
As a non-Haskell-knowing dev I only followed a portion of the
conversation leading up to this, but I think I'm tentatively liking the
direction this is moving. If I understand correctly, the suggestion is
to add, eventually, e.g.:
interface Comparable {
public function compare($other) : int; (returns -1, 0, 1)
}
interface ArithmeticStuff {
public function add($other) : ArithmeticStuff;
public function subtract($other) : ArithmeticStuff;
// ...
}
Am I following?
If so, I see the following (possibly surmountable) concerns:
-
The ArithmeticStuff interface (obviously better named) still has no
way to prevent mutation of $this or $other. I still see that as
user-space trainwreck waiting to happen unless there's some way to force
those methods to NOT modify $this in any way. -
Besides the issue with different classes that both implement the same
operator interface, as Andrea notes, it also would cause issues with
subclasses. To wit:
class Money implements Numberish {
protected $valueWithoutDecimals;
public function add($other) {
$ret = new Money($this->valueWithoutDecimals);
$ret->valueWIthoutDecimals += $other->valueWithoutDecmials;
return $ret;
}
}
class Dollars extends Money {}
$d1 = new Dollars(4);
$d2 = new Dollars(5);
$d3 = $d1 + $d2;
$d3 would now be an instance of Money, NOT Dollars. Sure, there are
better ways of writing that implementation that would avoid that problem
(cloning $this, using get_called_class()
, etc.) but that the potential
is there is worrisome.
Possible solution to 1 and 2: Instead of just passing $other, pass
$clone and $other, and block $this. To wit:
class Money implements Numberish {
protected $valueWithoutDecimals;
// Being static, $this doesn't exist although it's what $clone was
cloned from.
public static function add($clone, $other) {
$clone->valueWIthoutDecimals += $other->valueWithoutDecmials;
return $clone;
}
}
That way, the engine pre-clones $this for me, and I operate from that.
I could ignore $clone if I wanted to, but I cannot ever modify $this.
(That wouldn't address the mixed class question entirely, but does
address forced-immutability.)
- A long-standing source of aggravation for user-space devs is that
it's impossible to build an object that is a for-reals replacement for
arrays. We can implement ArrayAccess, Countable, Iterator, whatever,
but for all practical purposes they're still not even remotely close to
being arrays as far as the type system or array functions are concerned.
If what we're talking about here is adding more "act like a primitive"
interfaces for objects, we need to think through all of those little
nuances to make them as non-annoying as possible. (Eg, would an integer
pass a Numberish type check? In weak mode or strict mode? Can I pass a
Numberish to strpos()
? Can I pass a Stringish to strpos()
?) I don't
have answers for those but I would hope we'd think those through to
avoid the split existence that is collections/arrays.
should work on all number types. I don't think GMP currently overloads
any of these, though, and there is the possibility we might create
confusion if overloading extended to math functions.Which reinforces the point above that maybe before the are tackling the
operators in userspace we need to identify use cases and create
structure for them, and then it might be easier for us to avoid
inconsistencies. We're kind of moved forward with GMP without doing
that, but fortunately it's easier to fix such things while it's confined
to the engine/extensions. Once it's in userspace, changing it would be
way harder.In full agreement there. On that note, it's of course possible to
support overloading certain things, but only internally. It might be
reasonable to expose __assign_add (well, its C equivalent) to GMP but
not to userland, for instance.Regarding use cases, there are a few that come to mind.
One of these is userland number types. We don't, to the best of my
knowledge, currently have a decimal type as a PHP extension, so
applications which need it must look to userland implementations
(often backed by bcmath underneath). These would be more pleasant and
intuitive to use if they could use our normal arithmetic operators.
For such userland types, I imagine an interface with basic arithmetic
operations would work. Perhaps we might call it \Number. A question
might be whether to split apart integer and float operations like
Haskell does. I'm not sure what fits this use-case best, though it's
probably better not to require classes to implement what they don't
need, and just let people get an error if they do, say, ($decimal % 5).Another use case is userland string types. There's several projects
out there which wrap PHP strings in an object to provide an
object-oriented interface which is more pleasant to use than PHP's
string API. There is also the use-case of Unicode string wrappers,
since PHP still doesn't have a Unicode string type. They would benefit
from being able to overload the concatenation operator, and possibly
also the binary operations (^, & and |) which work on strings.
Everything else these types need is already overloadable with the
existing \ArrayAccess and \Iterator(Aggregate). There is a problem
here, through, in what we should require such classes to implement
beyond concatenation. Requiring nothing else leaves it open to abuse
(I can already imagine.
being used for the dot product), but
requiring, say, binary operations would be unfair (they're nonsense in
the context of Unicode), as would \ArrayAccess (Unicode strings may
have non-constant time indexing) or \Traversable (a Unicode string
class might choose to lack a default iterator to force the user to
consider the different ways a Unicode string can be used). Maybe we
could require \Countable, but even that has problems regarding
Unicode, non-constant time complexity especially. So, I'm not quite
sure what to do there.Thanks.
An example of a user-space string object is in Drupal 8: We added (very
late) a TranslatableString class, which contains a string template (with
placeholders), the placeholder replacements, and various cache-related
metadata. Off hand, I don't know if adding a Stringish interface to
that would be good or terrible. :-) It needs to be compiled into an
actual string (replacing the template with an alternate version for
another language if appropriate) during rendering so that we have the
right cache information available, a process that requires external
services. Off hand I have no idea what adding a concat() method or
position()/ArrayAccess method to that would do or if it would eat our
kittens, but I mention it here as another example in the wild.
--Larry Garfield
2016-01-05 2:04 GMT+03:00 Andrea Faulds ajf@ajf.me:
I agree that we could do something with interfaces. I would like to point
out that we actually already have an example of this, in the form of the
\ArrayAccess interface, which requires you to implement all the different
indexing operations at once. Unfortunately, though, \ArrayAccess doesn't
give us a precedent for dealing with the $this issue (in$a + $b
, who
gets called, how do we handle differing types, etc?), but it's a start.
Hi, Andrea and internals team!
Interface is a good way to implement new functionality for classes, but
operator overloading is language feature itself, so from my point of view,
it will be better to put this functionality into the magic method.
Personally, I don't like hundreds of __add, __mul, __etc methods, because
this will be ugly and will require addition of new methods when new
operators are included (eg __pow).
I want to suggest to add only one single method:
public function __operator(int $operatorKind, ...$operatorArgs);
This method will be called for every operator and class can check the
$operatorKind variable with simple check: if (Php\Operator::OP_DIVISION ===
$operatorKind) return $operatorArgs[0]->value / $operatorArgs[1]->value;
New values for operator type enumeration can be added later into new
versions of PHP easily.
Am 05.01.2016 um 15:29 schrieb Alexander Lisachenko lisachenko.it@gmail.com:
2016-01-05 2:04 GMT+03:00 Andrea Faulds ajf@ajf.me:
I agree that we could do something with interfaces. I would like to point
out that we actually already have an example of this, in the form of the
\ArrayAccess interface, which requires you to implement all the different
indexing operations at once. Unfortunately, though, \ArrayAccess doesn't
give us a precedent for dealing with the $this issue (in$a + $b
, who
gets called, how do we handle differing types, etc?), but it's a start.Hi, Andrea and internals team!
Interface is a good way to implement new functionality for classes, but
operator overloading is language feature itself, so from my point of view,
it will be better to put this functionality into the magic method.
Personally, I don't like hundreds of __add, __mul, __etc methods, because
this will be ugly and will require addition of new methods when new
operators are included (eg __pow).I want to suggest to add only one single method:
public function __operator(int $operatorKind, ...$operatorArgs);
This method will be called for every operator and class can check the
$operatorKind variable with simple check: if (Php\Operator::OP_DIVISION ===
$operatorKind) return $operatorArgs[0]->value / $operatorArgs[1]->value;
New values for operator type enumeration can be added later into new
versions of PHP easily.
I think an interface is a strongly superior way, especially as it'll allow you to hint that an implementation became incomplete when an operator has been added. (Also, how often are relevant operators added?)
At least as you aren't supposed to abuse the methods for other things than they're intended to. If you implement number operations, then you should implement the whole set of them.
If you implement a string class, you should implement the array access and the string concat.
Operators definitely shouldn't have specific context sensitive meaning (like printing when you bitshift (wtf, C++?)).
At least, we should assume that the operators are used in accord with their intended meaning.
Bob
Hi!
Interface is a good way to implement new functionality for classes, but
operator overloading is language feature itself, so from my point of view,
it will be better to put this functionality into the magic method.
No contradiction here. Language features can use interfaces, see
Throwable or ArrayAccess.
--
Stas Malyshev
smalyshev@gmail.com
Hi Sara,
Sara Golemon wrote:
Patricio Tarantino has asked me to help him propose Operator
Overloading in PHP 7.1 (based in part on my operator extension in
PECL). I think we can expose this to usespace as magic methods with
very little overhead (the runtime check and dispatch is already there,
after all).I do think that the "Future Expansion" section bears following through
with as well, but the basic set of methods already hooked for GMP would
be a nice start.While I would like to see operator overloading in PHP, I don't
particularly like the approach this RFC takes. It seems to take the
approach C++, Python and so on use where you can simply overload any
operator as you see fit. While that is arguably useful, it is open to
abuse. In C++, for example, you can join two file paths by dividing
them, or write to a stream by bitwise shifting it left by another
stream. In Python, you repeat a string by finding the product of it and
an integer. Abusing operator overloading like this harms readability, is
not necessarily intuitive and is anyway unnecessary: a function or
method would work just as well in these situations. Also, I'm not sure
it's a good fit for a dynamic language to make operators do different
things depending on their operand types. That just creates the
possibility of unpleasant surprises at runtime, and PHP has enough of
these already without the possibility of users creating more.Luckily, C++ and Python's approach is not our only option. I am quite a
fan of Haskell's approach to operator overloading, which is less prone
to abuse. In Haskell, a purely-functional programming language, certain
operators (and also certain math functions likeabs()
) are defined as
part of "typeclasses", somewhat akin to interfaces in classical
object-oriented languages like Java or PHP. These typeclasses group
related operations together, and a conforming implementation of that
typeclass must implement all the operations. For example, there is a
Num
typeclass[0] for numbers, which defines the addition, subtraction,
multiplication, negation, absolute value, sign and
conversion-from-integer operations. ExtendingNum
, there is a
Fractional
typeclass[1] for fractional number types, which adds
fractional division, reciprocal and conversion-from-rational operations.
There are likewise similar typeclasses for non-numbers, and other
descendents ofNum
for other kinds of numbers. The approach Haskell
takes here is not as prone to abuse because you cannot simply implement
operations as you please: you must implement them as a set. (Though,
granted, Haskell also discourages overloading abuse by actually letting
you define custom operators.) With this approach Haskell also achieves
something else useful, in that you can use typeclasses as a type
constraint on function parameters and return types. I think it would be
more worth pursuing a Haskell-style approach in PHP, most likely with a
hierarchy of magic interfaces.All that aside, I have some other issues with the RFC. The set of
overloadable operators proposed is very small, and I'm surprised it
doesn't even include the full set of number operations. But
additionally, if you were to fill in that set, I wonder if it might be
worthwhile making some of the math functions overloadable as well, since
otherwise you have an incomplete set of operators: %'s complement is
intdiv()
, not /, and the closest thing we have to a complement of / is
fmod()
. Likewise, sinceabs()
works on integers and floats, perhaps it
should work on all number types. I don't think GMP currently overloads
any of these, though, and there is the possibility we might create
confusion if overloading extended to math functions.Another concern I have is the possibility of having assign*
overloads. These would useful for avoiding having to instantiate a new
object, but there's potential for them to be implemented wrongly, or be
implemented without their corresponding normal operations. Since we
don't have any way to make PHP 4-style copy-on-write value classes at
the moment, I think we should just do the simpler thing and assume we're
dealing with completely immutable objects, and therefore not have
assign* methods. Similarly, I don't think should have overloads for
++ and -- (the proposed /(post|pre)_(inc|dec)/ methods). Heck, they're
probably not even that useful, because you could simply check in __add
or __sub if the operand's absolute value is 1.Regarding the possibility of comparison operators, again I think we
should go the simpler route. For <, <=, >, and >=, we only need one
overload method, perhaps __cmp, which returns the usual negative, zero
or positive value to indicate ordering. I can't see a benefit of having
a seperate methods for each of these, and it would introduce the risk of
them being implemented inconsistently, creating chaotic sorts. Similarly
for == and !=, we only need a single method, perhaps __is_equal, or
maybe we should not give them a separate method and group them with <,
<=, > and >= into __cmp, which would again have the benefit of
preventing inconsistent implementation. For === and !==, I would be wary
of letting them be overloaded, given they are currently "strict", and it
is useful to be able to check if two objects are the same instance,
something objects shouldn't need control over. == and !=, on the other
hand, compare content, which the objects themselves can probably decide
better than PHP's default behaviour.Anyway, I appreciate you making this RFC, if only to prompt discussion.
Thanks.
[0]
http://hackage.haskell.org/package/base-4.8.1.0/docs/Prelude.html#t:Num
[1]
http://hackage.haskell.org/package/base-4.8.1.0/docs/
Prelude.html#t:Fractional
It looks like I'm late to the party for this discussion, but I feel like
this is a similar concept to the already existing Comparable RFC <https://
wiki.php.net/rfc/comparable>, which in my opinion, has a much better
interface. Is this slightly more in-line with what you're thinking with
type classes?
I'd say this RFC should address the Comparable RFC since they are in
direct "conflict" (not quite the right word there). Perhaps instead group
relevant operators into magic interfaces like Comparable, Arithmetic, or
something along those lines.
I'm in favor of operator overloading, but it really needs to be done with
much care.
Hi Stephen,
Stephen Coakley wrote:
It looks like I'm late to the party for this discussion, but I feel like
this is a similar concept to the already existing Comparable RFC <https://
wiki.php.net/rfc/comparable>, which in my opinion, has a much better
interface. Is this slightly more in-line with what you're thinking with
type classes?I'd say this RFC should address the Comparable RFC since they are in
direct "conflict" (not quite the right word there). Perhaps instead group
relevant operators into magic interfaces like Comparable, Arithmetic, or
something along those lines.I'm in favor of operator overloading, but it really needs to be done with
much care.
Yes, the Comparable interface is the sort of thing I'm thinking about.
Doing things with interfaces isn't really a terribly novel idea anyway,
it's what we already do for ArrayAccess and such.
Thanks.
--
Andrea Faulds
https://ajf.me/
Back in the days when I created the first implementation of operator
overloading in the engine[1] I didn't see it fit for the language.
Meanwhile we have more type hints and stuff making it a bit less magic.
I still don't think this approach is really good. for instance we loose
associativity; taking the Complex example:
<?php
$a = new Complex(1, 2);
$b = 3;
echo $a + $b; // works
echo $b + $a; // will error out
?>
Of course this can easily be fixed by looking at the order, at least in
this simple case. In a more complex example where a function might
return an integer, double or complex number this becomes more
complicated.
For this to work we'd need non-member functions to do this and we need
function overloading ...
I believe we'll only get a half-baked solution and I prefer the current
state over half-baked.
johannes
Am 4.1.2016 um 20:39 schrieb Johannes Schlüter johannes@schlueters.de:
Back in the days when I created the first implementation of operator
overloading in the engine[1] I didn't see it fit for the language.
Meanwhile we have more type hints and stuff making it a bit less magic.
I still don't think this approach is really good. for instance we loose
associativity; taking the Complex example:<?php
$a = new Complex(1, 2);
$b = 3;
echo $a + $b; // works
echo $b + $a; // will error out
?>Of course this can easily be fixed by looking at the order, at least in
this simple case. In a more complex example where a function might
return an integer, double or complex number this becomes more
complicated.For this to work we'd need non-member functions to do this and we need
function overloading ...I believe we'll only get a half-baked solution and I prefer the current
state over half-baked.johannes
Then that must be very long ago, as, looking at code even as old as 5.6, http://lxr.php.net/xref/PHP_5_6/Zend/zend_operators.h#959 http://lxr.php.net/xref/PHP_5_6/Zend/zend_operators.h#959 clearly checks both operand for object with custom do_operation.
The only issue is when you try to compare two objects with each custom operation handlers, like, e.g. an instance of Complex and Fractional. To have that working, we couldn’t just use real fractionals, but would need a special ComplexFractional, as Fractional may not be aware of Complex.
This really is the limit of this abstraction… But I guess, it won’t be too much of an issue in real world.
Bob