[RFC] Comparable: the revenge

php.internals

Adam Harvey

11 years ago
Hi all, Those of you with long memories will remember that I proposed a Comparable interface way back in the pre-5.4 days, but withdrew it when it became obvious that there was no consensus for it as a feature and that a vote was likely to fail. RFC: https://wiki.php.net/rfc/comparable PR: https://github.com/php/php-src/pull/1097 Why reanimate it now, I hear you ask? I think that comparisons have only become more prominent in the language: we now have a spaceship operator for explicit comparisons, yet the behaviour of object comparisons can be obscure, to say the least, and the user has no control over how their objects are compared. At this stage, I intend to put this up for a vote on March 5 (vote ending March 12), with the obvious endgame being that this would be included in 7.0. Thanks, Adam

Levi Morrison

11 years ago
> Those of you with long memories will remember that I proposed a > Comparable interface way back in the pre-5.4 days, but withdrew it > when it became obvious that there was no consensus for it as a feature > and that a vote was likely to fail. > > RFC: https://wiki.php.net/rfc/comparable > PR: https://github.com/php/php-src/pull/1097 > > Why reanimate it now, I hear you ask? I think that comparisons have > only become more prominent in the language: we now have a spaceship > operator for explicit comparisons, yet the behaviour of object > comparisons can be obscure, to say the least, and the user has no > control over how their objects are compared. > > At this stage, I intend to put this up for a vote on March 5 (vote > ending March 12), with the obvious endgame being that this would be > included in 7.0.
If the responsibility of comparing two objects is pushed into the objects themselves then the ability to use different comparison criteria for the same two objects when placing them in two different structures is taken away. This is actually a very common problem I have seen people complain about in Java. Another issue: it allows comparing an object to non-objects (even though the stated goal is only to compare two objects of the same type): class MyClass implements Comparable { private $val = 0; function compareTo($a) { return $this->val <=> $a; } } $int = 10; $myClass = new MyClass(); $myClass <=> $int; // works $int <=> $myClass; // if this doesn't produce a warning it at least doesn't behave the same as the line above it Ultimately, when comparing two objects it is usually as part of some algorithm, so instead the Comparator interface could be used: interface Comparator { function compare($a, $b): int; } But even here I would rather just take a function instead of requiring it to be the instance of some interface: function sort($input, callable $comparator($a, $b): int) { /* … */ } All in all, I don't think the fundamental idea of the RFC is good. The Comparable interface is inferior to many other techniques of achieving the same goal of comparing two objects. I do want to thank you for taking the time to cite arguments, prior history and an alternative in the RFC. You have done a pretty good job on the RFC itself, in my opinion.

Adam Harvey

11 years ago
I don't want to get into a lengthy debate (you have your opinion; I have mine!), but to rebut a couple of specific points: On 19 February 2015 at 14:19, Levi Morrison <levim@php.net> wrote:
> Another issue: it allows comparing an object to non-objects (even > though the stated goal is only to compare two objects of the same > type):
This is intentional. The wording in the introduction is probably a little too specific to the object case — I'll fix that.
> class MyClass implements Comparable { > private $val = 0; > function compareTo($a) { > return $this->val <=> $a; > } > } > > $int = 10; > $myClass = new MyClass(); > $myClass <=> $int; // works > $int <=> $myClass; // if this doesn't produce a warning it at least > doesn't behave the same as the line above it
It does behave the same as the line above (with the result inverted, obviously) — MyClass::compareTo() is still called in this case. The only time ordering matters is if two objects are being compared, in which case the leftmost one is the one that has its compareTo() method called.
> But even here I would rather just take a function instead of requiring > it to be the instance of some interface: > > function sort($input, callable $comparator($a, $b): int) { > /* … */ > }
Fair, but the sorting case isn't the only one that matters, particularly with <=> now as part of the language.
> I do want to thank you for taking the time to cite arguments, prior > history and an alternative in the RFC. You have done a pretty good job > on the RFC itself, in my opinion.
Thanks. :) Adam

Levi Morrison

11 years ago
>> But even here I would rather just take a function instead of requiring >> it to be the instance of some interface: >> >> function sort($input, callable $comparator($a, $b): int) { >> /* … */ >> } > > Fair, but the sorting case isn't the only one that matters, > particularly with <=> now as part of the language.
For completeness: I advocate accepting a comparator in every case where custom comparison logic is needed, not just for sorting.

Pierre Joye

11 years ago
On Thu, Feb 19, 2015 at 1:40 PM, Adam Harvey <aharvey@php.net> wrote:
> Hi all, > > Those of you with long memories will remember that I proposed a > Comparable interface way back in the pre-5.4 days, but withdrew it > when it became obvious that there was no consensus for it as a feature > and that a vote was likely to fail. > > RFC: https://wiki.php.net/rfc/comparable > PR: https://github.com/php/php-src/pull/1097 > > Why reanimate it now, I hear you ask? I think that comparisons have > only become more prominent in the language: we now have a spaceship > operator for explicit comparisons, yet the behaviour of object > comparisons can be obscure, to say the least, and the user has no > control over how their objects are compared. > > At this stage, I intend to put this up for a vote on March 5 (vote > ending March 12), with the obvious endgame being that this would be > included in 7.0. > > Thanks, >
Thanks you :) I like it. It is simple and straightforward. I agree with other that it is easily done using a simple function (Adam's) but I do not like it much, less clear. Cheers,
-- Pierre @pierrejoye | http://www.libgd.org

Dennis Birkholz

11 years ago
Hi Adam, Am 19.02.2015 um 22:40 schrieb Adam Harvey:
> RFC: https://wiki.php.net/rfc/comparable > PR: https://github.com/php/php-src/pull/1097
I see a little problem here by allowing any comparison. Comparing is used for ordering, but you can easily construct ordering mechanisms that simply don't work: class A implements Comparable { public $value; function compareTo($other) { if ($other instanceof A) { return $this->value <=> $other->value; } else { return -1; } } } class B implements Comparable { public $value; function compareTo($other) { if ($other instanceof B) { return $this->value <=> $other->value; } else { return -1; } } } $a = new A(); $a->value = 1; $b = new B(); $b->value = 2; $a->compareTo($b); // -1 $b->compareTo($a); // -1 If you now have a mixed list of objects of A and B, the actual order in the list completely depends whether A's compareTo() method is called or B's compareTo() method. I think comparison should always be symmetric: $a < $b <=> $b > $a Otherwise the behavior is nearly unpredictable/random. As we have no method overloading, the only viable option I see is to only allow comparison of objects of the same type. Thanks Dennis

Markus Fischer

11 years ago
On 19.02.15 22:40, Adam Harvey wrote:
> Those of you with long memories will remember that I proposed a > Comparable interface way back in the pre-5.4 days, but withdrew it > when it became obvious that there was no consensus for it as a feature > and that a vote was likely to fail. > > RFC: https://wiki.php.net/rfc/comparable > PR: https://github.com/php/php-src/pull/1097
Afaik \DateTime object already implement a way to be used with <,>,etc. but they don't use an interface for that. $ php -r '$past = new DateTime("-1 month"); $future = new DateTime("+1 month"); var_dump($past < $future);' bool(true) $ php -r '$past = new DateTime("-1 month"); $future = new DateTime("+1 month"); var_dump($past > $future);' bool(false) Shouldn't they retroactively be adopted to that? Extend from that Interface, implement a compoareTo? - Markus

Nikita Popov

11 years ago
On Thu, Feb 19, 2015 at 10:40 PM, Adam Harvey <aharvey@php.net> wrote:
> Hi all, > > Those of you with long memories will remember that I proposed a > Comparable interface way back in the pre-5.4 days, but withdrew it > when it became obvious that there was no consensus for it as a feature > and that a vote was likely to fail. > > RFC: https://wiki.php.net/rfc/comparable > PR: https://github.com/php/php-src/pull/1097 > > Why reanimate it now, I hear you ask? I think that comparisons have > only become more prominent in the language: we now have a spaceship > operator for explicit comparisons, yet the behaviour of object > comparisons can be obscure, to say the least, and the user has no > control over how their objects are compared. > > At this stage, I intend to put this up for a vote on March 5 (vote > ending March 12), with the obvious endgame being that this would be > included in 7.0. >
Some technical notes on the RFC text: * "How it is ensured that if $a < $b then $b > $a?" The RFC claims that it is up to the developer to ensure this, however I don't think this is true. As far as I know PHP will implicitly enforce this. It would be good if you could double-check the behavior. * "If both $a and $b are objects with different compare functions, how it is determined whose function is used" Here you answer that the left operand wins. Once again I am unsure whether this is true - I'd assume that for > and >= the right operand wins. * I would appreciate a note that indicates how non-total / non-trichotomous comparisons can be implemented, namely by returning 1 irregardless of order. You can use a subset-relationship to illustrate this. * Have you considered the impact on exception-safety this may have? Did you verify that all places where compare_function (or derivatives) are used are exception-safe? Nikita