Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:102406 Return-Path: Mailing-List: contact internals-help@lists.php.net; run by ezmlm Delivered-To: mailing list internals@lists.php.net Received: (qmail 42301 invoked from network); 25 Jun 2018 01:57:22 -0000 Received: from unknown (HELO lists.php.net) (127.0.0.1) by localhost with SMTP; 25 Jun 2018 01:57:22 -0000 Authentication-Results: pb1.pair.com smtp.mail=rudolf.theunissen@gmail.com; spf=pass; sender-id=pass Authentication-Results: pb1.pair.com header.from=rtheunissen@php.net; sender-id=unknown Received-SPF: pass (pb1.pair.com: domain gmail.com designates 209.85.213.45 as permitted sender) X-PHP-List-Original-Sender: rudolf.theunissen@gmail.com X-Host-Fingerprint: 209.85.213.45 mail-vk0-f45.google.com Received: from [209.85.213.45] ([209.85.213.45:36227] helo=mail-vk0-f45.google.com) by pb1.pair.com (ecelerity 2.1.1.9-wez r(12769M)) with ESMTP id 9A/9F-13077-10C403B5 for ; Sun, 24 Jun 2018 21:57:21 -0400 Received: by mail-vk0-f45.google.com with SMTP id o138-v6so6935676vkd.3 for ; Sun, 24 Jun 2018 18:57:21 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:references:in-reply-to:from:date :message-id:subject:to; bh=j8K240CflJ2d7Y9BgUvT8tR6aXF8Ls438Uwtvvjpu5E=; b=mocJ+22c3jeHDnHjq6bGttNU7xbZU4h1il3ji1V7DYjbOINsIR4Ice2nBam60D/mnk a5A1j7ZNcCuiSycfjcWAYenFbFbn7pDfbTOA/FNjuU+Zch/+xP/7O+ravi0MfcRv/6Yr RKbNTxmySuXftc2pt+Urev15Zv3bv0BMMQvl7wWF64hWBiCMZqUEcQUFGweafMmKjPlN XqDKo7uKamzSJJyoG8K3Q87irhJH8eymkZ0/N39R1yK0jjgklG7lacgw3mXVNABZYITL qBMKkdpP2aPT0IiNKj50alv/9nfRK18v4QctMScadCxi3IBnjiuZKpjCrWngi9LWR6NZ yeKw== X-Gm-Message-State: APt69E3JReU+2QMZCR1PidER39Q0qrw4zeiajuNVi5spuFek1l0zwYjK BKdgUpK9exN5zywllsC5IcxI/cjr X-Google-Smtp-Source: ADUXVKL+KlBqrUBoWsK8lS6upPNZio6OrbazFcTMZxImS/PY8bqpWdlW9+T60PTMLLn2+ByVXZ++Fg== X-Received: by 2002:a1f:ac0e:: with SMTP id v14-v6mr5819150vke.32.1529891837778; Sun, 24 Jun 2018 18:57:17 -0700 (PDT) Received: from mail-ua0-f178.google.com (mail-ua0-f178.google.com. [209.85.217.178]) by smtp.gmail.com with ESMTPSA id d20-v6sm2770229ual.26.2018.06.24.18.57.16 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Sun, 24 Jun 2018 18:57:17 -0700 (PDT) Received: by mail-ua0-f178.google.com with SMTP id c23-v6so7630728uan.3 for ; Sun, 24 Jun 2018 18:57:16 -0700 (PDT) X-Received: by 2002:a9f:3047:: with SMTP id i7-v6mr7287409uab.189.1529891836702; Sun, 24 Jun 2018 18:57:16 -0700 (PDT) MIME-Version: 1.0 References: In-Reply-To: Date: Sun, 24 Jun 2018 21:57:05 -0400 X-Gmail-Original-Message-ID: Message-ID: To: internals@lists.php.net Content-Type: multipart/alternative; boundary="00000000000078b700056f6db3de" Subject: Re: [PHP-DEV] Equality and relative ordering of objects From: rtheunissen@php.net (Rudi Theunissen) --00000000000078b700056f6db3de Content-Type: text/plain; charset="UTF-8" > > 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 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? > --00000000000078b700056f6db3de--