[PHP-DEV] [RFC] Operator Overrides -- Lite Edition

Hello internals,

I’d like to introduce a new RFC: https://wiki.php.net/rfc/operator_overrides_lite which extends the GMP extension to support a limited set of operator overriding to developers. It’s designed to be limited and relatively simple, while still being quite powerful. It would only be available if you have the GMP extension installed and enabled, but would allow for creating powerful unit libraries (such as representing money, duration, etc).

I’m very interested in your feedback!

This was initiated from a discussion in another thread: https://externals.io/message/123872

Thanks for your time,

Rob Landers

On Fri, Jun 28, 2024 at 10:47 AM Rob Landers rob@bottled.codes wrote:

Hello internals,

I’d like to introduce a new RFC: https://wiki.php.net/rfc/operator_overrides_lite which extends the GMP extension to support a limited set of operator overriding to developers. It’s designed to be limited and relatively simple, while still being quite powerful. It would only be available if you have the GMP extension installed and enabled, but would allow for creating powerful unit libraries (such as representing money, duration, etc).

I’m very interested in your feedback!

This was initiated from a discussion in another thread: https://externals.io/message/123872

Thanks for your time,

Rob Landers

You probably have not actually looked at implementing this yet, so let me give you some advice:

  1. You probably are not yet aware that operands get reordered, or their ordering is not guaranteed, when a comparison occurs. This is because there is no op code (currently) for Greater Than or Greater Than Or Equal. Instead the comparison is reordered so that the operands are swapped and a Less Than or a Less Than Or Equal is performed. This is perfectly acceptable until you need to determine whether the left or right operand’s function needs to be called, such as with objects.
  2. The fact that the signature uses mixed for the typing removes a lot of the safety features the previous RFC had, and makes a lot of the concerns that stopped that RFC worse. In PHP currently, an object used with any non-comparison operand results in a TypeError. In the previous proposal, this behavior was preserved even for objects which implement overloads unless they specifically listed the type as accepted in the overload definition. What this did is ensure that anything OTHER than a TypeError guaranteed the developer that they were dealing with an operator overload that they could go inspect.
  3. The private/protected distinction is fairly meaningless for the functions that implement overloads, because the privacy of the function is ignored completely when it is executed to evaluate an operator.
  4. The static distinction is also fairly meaningless, as in PHP there is no situation possible where an operator overload can occur WITHOUT it operating on objects themselves.
  5. Your voting choices actually constitute something that is not allowed for RFCs. A ‘yes’ vote allows operator overloads for GMP, a ‘no’ vote changes GMP to final. There is no option here to leave PHP as it currently is, which is what a ‘no’ vote should mean.
  6. The comparable function you propose doesn’t actually have an operator it corresponds to. There is no operator in PHP for “is the left value comparable with the right value”. There are operators for comparisons themselves, which I assume you meant, but a bool is insufficient as a return type for that.

There’s probably more, but this is a start for you to make some improvements and changes. However, I had to warn you before you put in too much effort that I am fairly certain this RFC has very nearly zero chance of passing.

Jordan

On Fri, Jun 28, 2024, at 20:39, Jordan LeDoux wrote:

On Fri, Jun 28, 2024 at 10:47 AM Rob Landers rob@bottled.codes wrote:

Hello internals,

I’d like to introduce a new RFC: https://wiki.php.net/rfc/operator_overrides_lite which extends the GMP extension to support a limited set of operator overriding to developers. It’s designed to be limited and relatively simple, while still being quite powerful. It would only be available if you have the GMP extension installed and enabled, but would allow for creating powerful unit libraries (such as representing money, duration, etc).

I’m very interested in your feedback!

This was initiated from a discussion in another thread: https://externals.io/message/123872

Thanks for your time,

Rob Landers

You probably have not actually looked at implementing this yet, so let me give you some advice:

  1. You probably are not yet aware that operands get reordered, or their ordering is not guaranteed, when a comparison occurs. This is because there is no op code (currently) for Greater Than or Greater Than Or Equal. Instead the comparison is reordered so that the operands are swapped and a Less Than or a Less Than Or Equal is performed. This is perfectly acceptable until you need to determine whether the left or right operand’s function needs to be called, such as with objects.

I actually did know that from implementing something like this for a client. That is why I chose “comparable” to let the developer decide if two objects are comparable or not. For example, money probably can’t be equal, less than, or more than a plain number, distance, or time. All objects must be comparable for the comparison to succeed. I have updated the RFC.

  1. The fact that the signature uses mixed for the typing removes a lot of the safety features the previous RFC had, and makes a lot of the concerns that stopped that RFC worse. In PHP currently, an object used with any non-comparison operand results in a TypeError. In the previous proposal, this behavior was preserved even for objects which implement overloads unless they specifically listed the type as accepted in the overload definition. What this did is ensure that anything OTHER than a TypeError guaranteed the developer that they were dealing with an operator overload that they could go inspect.

The mixed type is on purpose until I have an implementation. I haven’t decided if both will always be a \GMP object or if float|int|string might get thrown in there too. I want the former, but I’ll have to see what I can do.

I’ll have to look into that exception handling, but I don’t want to break the current behavior of GMP or make any big changes to support this.

  1. The private/protected distinction is fairly meaningless for the functions that implement overloads, because the privacy of the function is ignored completely when it is executed to evaluate an operator.

Hmm. I like the idea of protected, because it gives a structure to it that is apparent and usable right from the IDE. You just “fill in the blanks” or stick with the default behavior.

  1. The static distinction is also fairly meaningless, as in PHP there is no situation possible where an operator overload can occur WITHOUT it operating on objects themselves.

For this, that is the wrong approach. The actual behavior is on the type, not the instance. The object instances may not even know their value, they merely represent the value.

  1. Your voting choices actually constitute something that is not allowed for RFCs. A ‘yes’ vote allows operator overloads for GMP, a ‘no’ vote changes GMP to final. There is no option here to leave PHP as it currently is, which is what a ‘no’ vote should mean.

Yeah. I was actually wondering about this… I believe Gina made it kinda clear that it was accidentally non-final. So I was just saving everyone an RFC — maybe I will make it a secondary vote for the case where the RFC is declined. I’ve now updated the RFC.

  1. The comparable function you propose doesn’t actually have an operator it corresponds to. There is no operator in PHP for “is the left value comparable with the right value”. There are operators for comparisons themselves, which I assume you meant, but a bool is insufficient as a return type for that.

In the engine, there’s just a compare function for internal overrides. So we just check that everyone agrees that the two objects are comparable and then pass it on to “business as usual.”

There’s probably more, but this is a start for you to make some improvements and changes. However, I had to warn you before you put in too much effort that I am fairly certain this RFC has very nearly zero chance of passing.

Jordan

I have absolutely zero faith this will pass, but I will give it a fair shot. It’s the least I can do.

— Rob

On Fri, Jun 28, 2024 at 12:55 PM Rob Landers rob@bottled.codes wrote:

  1. The private/protected distinction is fairly meaningless for the functions that implement overloads, because the privacy of the function is ignored completely when it is executed to evaluate an operator.

Hmm. I like the idea of protected, because it gives a structure to it that is apparent and usable right from the IDE. You just “fill in the blanks” or stick with the default behavior.

I do not understand how the visibility has any impact on the usability you are seeking to provide.

  1. The static distinction is also fairly meaningless, as in PHP there is no situation possible where an operator overload can occur WITHOUT it operating on objects themselves.

For this, that is the wrong approach. The actual behavior is on the type, not the instance. The object instances may not even know their value, they merely represent the value.

A GMP object instance that does not know its value? What are you even talking about? Can you show me some code explaining what you mean? I had literally months of this argument for the operator overloads RFC, and studied the overload implementations in six other languages as part of writing that RFC, I feel like I understand this topic fairly well. But I do not understand what you are saying here.

  1. The comparable function you propose doesn’t actually have an operator it corresponds to. There is no operator in PHP for “is the left value comparable with the right value”. There are operators for comparisons themselves, which I assume you meant, but a bool is insufficient as a return type for that.

In the engine, there’s just a compare function for internal overrides. So we just check that everyone agrees that the two objects are comparable and then pass it on to “business as usual.”

I’m aware of how the compare handler for class entries and zend_compare interact. What I am saying is that your design is insufficient for <=>. You cannot return false from this method to mean uncomparable, and true to mean comparable. The zend_compare function can validly return 0 or -1, with the -1 being used for both less than OR greater than because the operands are reordered to always be a less than comparison. Then 1, which normally is used for greater than, is used to mean uncomparable.

If you alter GMP so that the compare handler directly calls the class entry for this “comparable” function, you will be mixing multiple return semantic meanings, assuming you can construct a way to make the values normalize. If you implemented “comparable” in the way you are describing, $obj1 == $obj2 would ALWAYS be true, no matter what their values are, completely changing the meaning of the equality operator for GMP.

As you are proposing this without any genuine expectation you can pass it, this will be the last energy I will invest into helping.

Jordan

On Sat, Jun 29, 2024, at 11:01, Rob Landers wrote:

On Sat, Jun 29, 2024, at 02:13, Jordan LeDoux wrote:

On Fri, Jun 28, 2024 at 12:55 PM Rob Landers rob@bottled.codes wrote:

  1. The private/protected distinction is fairly meaningless for the functions that implement overloads, because the privacy of the function is ignored completely when it is executed to evaluate an operator.

Hmm. I like the idea of protected, because it gives a structure to it that is apparent and usable right from the IDE. You just “fill in the blanks” or stick with the default behavior.

I do not understand how the visibility has any impact on the usability you are seeking to provide.

I guess it depends on what you mean by usability. From a technical standpoint, it has zero usability, but from a dev-ex standpoint, it has a huge amount of usability.

If we add these as protected methods to the base class, I merely need to write “protected static function” in my IDE and I will see all the methods I can write. It also lays bare “how it works” for a PHP developer without any magic, making it easier to document.

  1. The static distinction is also fairly meaningless, as in PHP there is no situation possible where an operator overload can occur WITHOUT it operating on objects themselves.

For this, that is the wrong approach. The actual behavior is on the type, not the instance. The object instances may not even know their value, they merely represent the value.

A GMP object instance that does not know its value? What are you even talking about? Can you show me some code explaining what you mean? I had literally months of this argument for the operator overloads RFC, and studied the overload implementations in six other languages as part of writing that RFC, I feel like I understand this topic fairly well. But I do not understand what you are saying here.

Heh, yeah, it’s kinda weird. Let me explain. The GMP class hides its value in a “private” member (because the value isn’t actually a number, but a GMP resource), so unless the programmer also sets the value to something they have access to, they won’t know the value (but they can always cast $this to a number or operate on it directly). The only way they could get the value is to cast $this to float, which may lose some precision. The idea here is to “write the rules” where the value doesn’t matter, or if it does, embed that as part of the rules.

For example, imagine we want to create a Field class, that takes a range for the field and keeps the value in the field. It might look something like this:

sigh: not enough coffee again and I saw the blunder as soon as I sent it. Here’s the more correct implementation.

class IntField {

public function __construct(private int $max, int $value) {

parent::construct($value, 10);

}

protected static function add($left, $right): self {

// todo: guard that left can be added to right – ie, both are integers

$result = parent::add($left, $right);

if ($result >= $left->max) return new IntField($left->max, $result % $left->max);

return new IntField($left->max, $result);

}

// todo: remaining implementation

}

I actually had a bit of a long-thought about it, and I think this is simpler (both to implement and to use) than the traditional approach, and more powerful. With the more traditional approach, how do define communitive rules? You are bound by traditional mathematics, more-or-less. From working in cryptography, a long time ago now, I can say that there are non-communitive rings where having access to both “left” and “right” can allow you to handle this quite well.

  1. The comparable function you propose doesn’t actually have an operator it corresponds to. There is no operator in PHP for “is the left value comparable with the right value”. There are operators for comparisons themselves, which I assume you meant, but a bool is insufficient as a return type for that.

In the engine, there’s just a compare function for internal overrides. So we just check that everyone agrees that the two objects are comparable and then pass it on to “business as usual.”

I’m aware of how the compare handler for class entries and zend_compare interact. What I am saying is that your design is insufficient for <=>. You cannot return false from this method to mean uncomparable, and true to mean comparable. The zend_compare function can validly return 0 or -1, with the -1 being used for both less than OR greater than because the operands are reordered to always be a less than comparison. Then 1, which normally is used for greater than, is used to mean uncomparable.

Ah, I mean that it calls this as a guard, before ever doing a comparison, not that this output will be used for comparison itself. This is deliberate, to keep it simple. If I get feedback that comparison should be implemented vs. a guard for comparison, I’d be happy to add it.

As you are proposing this without any genuine expectation you can pass it, this will be the last energy I will invest into helping.

I didn’t mean it how I think you are taking it. To expand a bit on what I meant, “we” (as in people who want this feature, like myself) can only create RFCs for it. Maybe one day, the voters will change their mind, “we” will find an implementation they agree with, or they’ll forget to vote “no” while enough people vote “yes.” So, yes, I genuinely want this feature and I want to propose a feature that works and is the best I can come up with; at the same time, I don’t expect it to pass, but I do hope that negative feedback will drive the feature to a compromise or solution that works. The only way to get there is by failing.

— Rob

— Rob

On Sat, Jun 29, 2024, at 02:13, Jordan LeDoux wrote:

On Fri, Jun 28, 2024 at 12:55 PM Rob Landers rob@bottled.codes wrote:

  1. The private/protected distinction is fairly meaningless for the functions that implement overloads, because the privacy of the function is ignored completely when it is executed to evaluate an operator.

Hmm. I like the idea of protected, because it gives a structure to it that is apparent and usable right from the IDE. You just “fill in the blanks” or stick with the default behavior.

I do not understand how the visibility has any impact on the usability you are seeking to provide.

I guess it depends on what you mean by usability. From a technical standpoint, it has zero usability, but from a dev-ex standpoint, it has a huge amount of usability.

If we add these as protected methods to the base class, I merely need to write “protected static function” in my IDE and I will see all the methods I can write. It also lays bare “how it works” for a PHP developer without any magic, making it easier to document.

  1. The static distinction is also fairly meaningless, as in PHP there is no situation possible where an operator overload can occur WITHOUT it operating on objects themselves.

For this, that is the wrong approach. The actual behavior is on the type, not the instance. The object instances may not even know their value, they merely represent the value.

A GMP object instance that does not know its value? What are you even talking about? Can you show me some code explaining what you mean? I had literally months of this argument for the operator overloads RFC, and studied the overload implementations in six other languages as part of writing that RFC, I feel like I understand this topic fairly well. But I do not understand what you are saying here.

Heh, yeah, it’s kinda weird. Let me explain. The GMP class hides its value in a “private” member (because the value isn’t actually a number, but a GMP resource), so unless the programmer also sets the value to something they have access to, they won’t know the value (but they can always cast $this to a number or operate on it directly). The only way they could get the value is to cast $this to float, which may lose some precision. The idea here is to “write the rules” where the value doesn’t matter, or if it does, embed that as part of the rules.

For example, imagine we want to create a Field class, that takes a range for the field and keeps the value in the field. It might look something like this:

class IntField {

public function __construct(private int $max, int $value) {

parent::construct($value, 10);

}

public function add($left, $right): self {

// todo: guard that left can be added to right – ie, both are integers

$result = parent::add($left, $right);

if ($result >= $this->max) return new IntField($this->max, $result % $this->max);

return new IntField($this->max, $result);
}

// todo: remaining implementation
}

I actually had a bit of a long-thought about it, and I think this is simpler (both to implement and to use) than the traditional approach, and more powerful. With the more traditional approach, how do define communitive rules? You are bound by traditional mathematics, more-or-less. From working in cryptography, a long time ago now, I can say that there are non-communitive rings where having access to both “left” and “right” can allow you to handle this quite well.

  1. The comparable function you propose doesn’t actually have an operator it corresponds to. There is no operator in PHP for “is the left value comparable with the right value”. There are operators for comparisons themselves, which I assume you meant, but a bool is insufficient as a return type for that.

In the engine, there’s just a compare function for internal overrides. So we just check that everyone agrees that the two objects are comparable and then pass it on to “business as usual.”

I’m aware of how the compare handler for class entries and zend_compare interact. What I am saying is that your design is insufficient for <=>. You cannot return false from this method to mean uncomparable, and true to mean comparable. The zend_compare function can validly return 0 or -1, with the -1 being used for both less than OR greater than because the operands are reordered to always be a less than comparison. Then 1, which normally is used for greater than, is used to mean uncomparable.

Ah, I mean that it calls this as a guard, before ever doing a comparison, not that this output will be used for comparison itself. This is deliberate, to keep it simple. If I get feedback that comparison should be implemented vs. a guard for comparison, I’d be happy to add it.

As you are proposing this without any genuine expectation you can pass it, this will be the last energy I will invest into helping.

I didn’t mean it how I think you are taking it. To expand a bit on what I meant, “we” (as in people who want this feature, like myself) can only create RFCs for it. Maybe one day, the voters will change their mind, “we” will find an implementation they agree with, or they’ll forget to vote “no” while enough people vote “yes.” So, yes, I genuinely want this feature and I want to propose a feature that works and is the best I can come up with; at the same time, I don’t expect it to pass, but I do hope that negative feedback will drive the feature to a compromise or solution that works. The only way to get there is by failing.

— Rob

On Sat, Jun 29, 2024, at 02:13, Jordan LeDoux wrote:

  1. The static distinction is also fairly meaningless, as in PHP there is no situation possible where an operator overload can occur WITHOUT it operating on objects themselves.

For this, that is the wrong approach. The actual behavior is on the type, not the instance. The object instances may not even know their value, they merely represent the value.

A GMP object instance that does not know its value? What are you even talking about? Can you show me some code explaining what you mean? I had literally months of this argument for the operator overloads RFC, and studied the overload implementations in six other languages as part of writing that RFC, I feel like I understand this topic fairly well. But I do not understand what you are saying here.

A few minutes ago, I sent an email where I accidentally made the code non-static, and I think I see the merit in what you are saying. It felt so natural to use $this that I didn’t even realize I was doing it wrong.

So, looking at your RFC and mine, I think this can be improved.

What would you suggest it look like and then we can work backwards from there?

Hi,

On Sat, Jun 29, 2024, at 02:13, Jordan LeDoux wrote:

  1. The static distinction is also fairly meaningless, as in PHP there is no situation possible where an operator overload can occur WITHOUT it operating on objects themselves.

For this, that is the wrong approach. The actual behavior is on the type, not the instance. The object instances may not even know their value, they merely represent the value.

A GMP object instance that does not know its value? What are you even talking about? Can you show me some code explaining what you mean? I had literally months of this argument for the operator overloads RFC, and studied the overload implementations in six other languages as part of writing that RFC, I feel like I understand this topic fairly well. But I do not understand what you are saying here.

A few minutes ago, I sent an email where I accidentally made the code non-static, and I think I see the merit in what you are saying. It felt so natural to use $this that I didn’t even realize I was doing it wrong.

So, looking at your RFC and mine, I think this can be improved.

What would you suggest it look like and then we can work backwards from there?

Here are my thoughts on your code.

In theory, inheriting from this “improved GMP class” would allow overloading of computational operators.

In effect, this acts like a “calcable interface”, with the constructor passing in meaningless values ​​to the parent constructor and the add method allowing the user to freely modify the return value.

This means that virtually any userland class can use the operator overloading feature via a “hack”.

This approach is completely wrong. Rather than proposing this as is, it would be more constructive to propose support for operator overloading.

Regards,

Saki

On Jun 29, 2024, at 10:16, Larry Garfield <larry@garfieldtech.com> wrote:

For clarity (since I know from experience it's helpful to RFC authors to have a concrete sense of votes in advance): I will be voting No on this RFC. As both Jordan and Saki have explained, it's a hideous hack that doesn't look like it would even work, much less be wise. I'd much rather take a second swing at Jordan's original operator overloading RFC, which I supported and still support. Let's do it right.

I agree with Larry that I would rather take another look at an overall operator overloading RFC than to implement this in one specific extension.

Cheers,
Ben

On Sat, Jun 29, 2024, at 7:28 AM, Saki Takamachi wrote:

Hi,

On Sat, Jun 29, 2024, at 02:13, Jordan LeDoux wrote:

4. The `static` distinction is also fairly meaningless, as in PHP there is no situation possible where an operator overload can occur WITHOUT it operating on objects themselves.

For this, that is the wrong approach. The actual behavior is on the type, not the instance. The object instances may not even know their value, they merely represent the value.

A GMP object instance that does not know its value? What are you even talking about? Can you show me some code explaining what you mean? I had literally months of this argument for the operator overloads RFC, and studied the overload implementations in six other languages as part of writing that RFC, I feel like I understand this topic fairly well. But I do not understand what you are saying here.

A few minutes ago, I sent an email where I accidentally made the code non-static, and I think I see the merit in what you are saying. It felt so natural to use $this that I didn't even realize I was doing it wrong.

So, looking at your RFC and mine, I think this can be improved.

What would you suggest it look like and then we can work backwards from there?

Here are my thoughts on your code.

In theory, inheriting from this "improved GMP class" would allow
overloading of computational operators.

In effect, this acts like a "calcable interface", with the constructor
passing in meaningless values ​​to the parent constructor and the add
method allowing the user to freely modify the return value.

This means that virtually any userland class can use the operator
overloading feature via a "hack".

This approach is completely wrong. Rather than proposing this as is, it
would be more constructive to propose support for operator overloading.

Regards,

Saki

For clarity (since I know from experience it's helpful to RFC authors to have a concrete sense of votes in advance): I will be voting No on this RFC. As both Jordan and Saki have explained, it's a hideous hack that doesn't look like it would even work, much less be wise. I'd much rather take a second swing at Jordan's original operator overloading RFC, which I supported and still support. Let's do it right.

--Larry Garfield

On Friday, 28 June 2024 at 18:46, Rob Landers <rob@bottled.codes> wrote:

Hello internals,

I'd like to introduce a new RFC: PHP: rfc:operator_overrides_lite which extends the GMP extension to support a limited set of operator overriding to developers. It's designed to be limited and relatively simple, while still being quite powerful. It would only be available if you have the GMP extension installed and enabled, but would allow for creating powerful unit libraries (such as representing money, duration, etc)

The design of this RFC is non-sensical.

If you want to do operator overloading via an extension, then create a dedicated extension instead of piggy-backing on an already existing extension,
this makes even less sense considering the extension is not always available.

You wouldn't even need an RFC as you could just write an extension and publish it on PECL and see if people adopt it.

As such I will be voting no on this proposal.
Sincerely,
Gina P. Banyard