rfc:comparable

This is an old revision of the document!


Request for Comments: Comparable

This RFC offers a Comparable interface which can be used to implement userspace ordering of objects.

Introduction

Many other languages offer the ability to provide a method on a class which will be used when instances of that class are compared via comparison or equality operators. For example, Java provides the Comparable interface, while Python provides the __cmp__ magic method.

Interface

Given the inspiration that PHP has drawn from Java in other areas of object orientation, the chosen API mirrors that of Java, with the exception of the Java-specific generic functionality. Expressed as a PHP interface, it would look like this:

<?php
interface Comparable {
    public function compareTo($o);
}
?>

Sample Code

A minimal class implementing this may look as follows:

<?php
class FuzzyException extends Exception {}
 
class Fuzzy implements Comparable {
    const TOLERANCE = 0.0001;
 
    public function __construct($value) {
        $this->value = (float) $value;
    }
 
    public function compareTo($value) {
        if (!$value instanceof Fuzzy) {
            throw new FuzzyException('Can only compare to other Fuzzy values');
        }
 
        $diff = $this->value - $value->value;
 
        if ($diff > self::TOLERANCE) {
            return 1;
        }
        elseif ($diff < -self::TOLERANCE) {
            return -1;
        }
 
        return 0;
    }
}
 
$a = new Fuzzy(1.23);
$b = new Fuzzy(2.34);
$c = new Fuzzy(2.340000001);
 
var_dump($a > $b);      // prints bool(false)
var_dump($a <= $b);     // prints bool(true)
var_dump($c == $b);     // prints bool(true), since it's within the tolerance
var_dump($c == 'foo');  // throws a FuzzyException
?>

Implementation

The patch below implements the Comparable interface within the Zend Engine in the same manner as interfaces such as Serializable. The changes to the Zend Engine are limited to the zend_interfaces.c and zend_operators.c files, and merely involve extending the zend_compare_objects() function to check if the first object implements the Countable interface, and if so, to call the compareTo() method, updating compare_function() to call zend_compare_objects() when appropriate, and the definition of the interface itself. Comparison cases not involving objects are unaffected.

FAQ

Is only $a checked for Comparable or also $b?

Both $a and $b are checked.

How it is ensured that if $a < $b then $b > $a?

That's up to the userspace developer to get right when they're implementing their compareTo method. I expect the manual would have a dire warning about the possible consequences of not making them equivalent.

Would sorting work with it?

Yes.

If both $a and $b are objects with different compare functions, how it is determined whose function is used? Note that operators like == are assumed to be commutative, and less/more operators are assumed to be commutative in pairs, like above.

The left operand wins, so $a.

What if the objects are not of the same class? What if one is of a subclass of the other? Is equality not symmetric?

Equality is symmetric to the extent that $a->compareTo($b) should return the inverse of $b->compareTo($a). Equality is not required to be symmetric in terms of both objects sharing the same compareTo() method, and comparisons across different class hierarchies are explicitly allowed.

How do you interact with 'get' and 'cast_object'/'__tostring'?

The tests for whether the operands implement Comparable occur before any potential calls to get and cast_object.

Concerns

I'll attempt to summarise the key arguments against this below. Please feel free to edit this if you don't feel your position is accurately represented; I'm trying to be impartial, but obviously I also want to see this succeed.

  • It's operator overloading; something PHP has historically avoided as a design choice.
  • Comparison of objects with non-objects may become confusing, particularly if scalar casting is added to the mix.
  • Comparison of objects with objects of a different class may be confusing or ill-defined.

Alternative Approaches

Traits

Stas suggests the following as an alternative to overloading the operator:

As a side note, if we have traits we might instead think of having Comparable trait or interface or both, which would declare having compareTo() standard feature (as Java does) without messing with the engine and overloading operators. Surely, it would be more verbose, but that might be a good thing.

Patches

Changelog

  • 2010-10-01: Revised patch to remove SPL dependency; added a few questions and answers from the mailing list and a list of concerns raised.
  • 2010-09-30: Initial proposal.
rfc/comparable.1285922962.txt.gz · Last modified: 2017/09/22 13:28 (external edit)