>
> Since this is a method, and not just a callback passed somewhere, could
> we simply mandate that the signature has an "int" return type?


We can error internally if the return value wasn't an integer, and the user
is welcome
to add a `: int` to the signature, but I'm not sure if you can specify
argument info for magic methods.

My only concern is what the user would return when ordering doesn't make
sense. For example:

```
class Shape {
    ...

    public function __compareTo(Shape $other): int {
        if ($this->name === $other->name) {
            return 0;
        }

        /**
         * There's no logical ordering for a shape!
         * I have to either:
         *     - Compare alphabetically by name
         *     - Let the LHS win and return -1.
         *
         * To indicate that the other shape DOES NOT equal this
         * shape, we would need to return either 1 or -1.
         *
         * In cases where neither of them make sense logically,
         * should we force the user to pick one or do we support
         * a nullable return which behaves like returning -1?
         */

         return -1; // Arbitrary
    }
}
```

A nullable integer return type to achieve this would be something like this:

```
class Shape {
    ...

    public function __compareTo(Shape $other): ?int {
        if ($this->name === $other->name) {
            return 0;
        }

        /**
         * Return nothing because there's no logical direction.
         * This is the equivalent of returning -1 because the RHS
         * will always win. Sorting an array of shapes will have no
         * effect but functions like in_array will work as expected.
         */
    }
}
```

There is one more tricky case when it comes to scalar values, and this is
why the "fall back
to default behaviour on `null`" came about:

*What happens when we compare an object to `null` or `false`?*

If we go with the `: ?int` scheme where returning `null` is the same as
returning -1, it would
mean that `Object < false` would return `true`, which is strange.

If we go with the `: int` scheme, we force the user to provide an arbitrary
direction in cases
where ordering isn't applicable, which really shouldn't be necessary.

But, if we change the behaviour of the `null` return to fall back to
default behaviour, we can
partially override comparability where applicable, and avoid weird cases
like `Object < false`.

However, it might not be clear to the user that default behaviour is in
effect, which may lead
to bugs that are difficult to debug or behaviour that is difficult to
trace. At this stage I'm
confident that a nullable integer return type is the way to go, we just
have to determine how
to handle the null case.

The way I see it, we have two options:

1. Fall through without warning, because it's documented and consistent.
The user can throw
    an exception themselves if their class is compared to something
unexpected.

2. Fall through with a warning or notice, because it's probably unintended
behaviour and the
    user should know about it.

```
class Shape {
    ...

    public function __compareTo($other) {
        if ($other instanceof Shape) {
            return $this->name <=> $other->name
        }

        /**
         * Return nothing because we don't know how to
         * compare against other types, and we're happy
         * to fall back to default behaviour.
         *
         * Alternatively, the user can type-hint Shape
         * which will guard against unintended comparison
         * and make the instanceof check redundant.
         */
    }
}
```

At this stage, my vote would go for a mixed return type where the `null`
case falls
through to default behaviour without notice. All return values will be
normalised to
-1, 0 and 1, but `null` will fall through.

I'm going to draft an RFC based on this approach with some clear examples.
:)

On Sun, 24 Jun 2018 at 16:31, Rudi Theunissen <rtheunis...@php.net> wrote:

> Other languages (most? all?) separate equality and ordering for this
>> reason.
>
>
> Java doesn't really separate them. Their `==` always checks object
> reference so is like PHP's ===.
> But they do have the .equals() method on all objects (our ==) and the
> collections use that for equality.
>
> In these schemes I am not understanding how to specify that something
>> is not equal, not ordered, and should not fall back to something else.
>> Consider things like UUIDs and other identifiers where equality is
>> desired and ordering does not make sense.
>
>
> For something to be not equal, you return anything but 0. If something is
> not ordered, you return -1 (LHS wins).
> If you don't want something to fall back to any default behaviour, just
> return an integer and not NULL.
>
> The realistic case, in my opinion, is that there will always be *some*
> property to order by, whether it's the object's
> numeric value, a string that can be alphabetical, or a time based element.
> We can argue that if the object doesn't
> have a defined, logical ordering, then it also wouldn't make sense to
> order a collection of them in the first place.
>
> So with that in mind, maybe it's okay to take "NULL falls back to default
> behaviour" idea out, and do a normalisation
> on whatever the user returns. If it's NULL, then that'll be 0, and the
> value will be equal. The only thing I don't like about
> that is that the user didn't specify "equal" with a 0, they returned the
> absence of a direction, and we should therefore
> either throw an exception internally, or fall back to the current
> comparison behaviour (comparing properties).
>
> The NULL return is a special case for me. Anything else is *some* value
> that can be normalised ($v ? ($v < 0 ? -1 : 1) : 0).
>
> Therefore, I believe it comes down to a simply question: how do we handle
> the case where __compareTo returns NULL?
>

Reply via email to