[swift-dev] Rationalizing FloatingPoint conformance to Equatable
Xiaodi Wu
xiaodi.wu at gmail.com
Wed Nov 1 15:43:32 CDT 2017
On Wed, Nov 1, 2017 at 13:47 David Sweeris <davesweeris at mac.com> wrote:
>
> On Nov 1, 2017, at 10:21 AM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>
>
> On Tue, Oct 31, 2017 at 23:43 David Sweeris <davesweeris at mac.com> wrote:
>
>>
>> On Oct 31, 2017, at 20:58, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>>
>> On Tue, Oct 31, 2017 at 10:50 PM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>>
>>> On Tue, Oct 31, 2017 at 10:23 PM, David Sweeris <davesweeris at mac.com>
>>> wrote:
>>>
>>>>
>>>> On Oct 31, 2017, at 7:26 PM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>>>>
>>>> On Tue, Oct 31, 2017 at 5:56 PM, David Sweeris <davesweeris at mac.com>
>>>> wrote:
>>>>
>>>>>
>>>>> On Oct 31, 2017, at 09:07, Stephen Canon via swift-dev <
>>>>> swift-dev at swift.org> wrote:
>>>>>
>>>>> [Replying to the thread as a whole]
>>>>>
>>>>> There have been a bunch of suggestions for variants of `==` that
>>>>> either trap on NaN or return `Bool?`. I think that these suggestions result
>>>>> from people getting tunnel-vision on the idea of “make FloatingPoint
>>>>> equality satisfy desired axioms of Equatable / Comparable”. This is
>>>>> misguided. Our goal is (should be) to make a language usable by developers;
>>>>> satisfying axioms is only useful in as much as they serve that goal.
>>>>>
>>>>> Trapping or returning `Bool?` does not make it easier to write correct
>>>>> concrete code, and it does not enable writing generic algorithms that
>>>>> operate on Comparable or Equatable. Those are the problems to be solved.
>>>>>
>>>>> Why do they not help write correct concrete code? The overwhelming
>>>>> majority of cases in which IEEE 754 semantics lead to bugs are due to
>>>>> non-reflexivity of equality, so let’s focus on that. In the cases where
>>>>> this causes a bug, the user has code that looks like this:
>>>>>
>>>>> // Programmer fails to consider NaN behavior.
>>>>> if a == b {
>>>>> }
>>>>>
>>>>> but the correct implementation would be:
>>>>>
>>>>> // Programmer has thought about how to handle NaN here.
>>>>> if a == b || (a.isNaN && b.isNaN) {
>>>>> }
>>>>>
>>>>> W.r.t ease of writing correct *concrete* code, the task is to make
>>>>> *this* specific case cleaner and more intuitive. What does this look like
>>>>> under other proposed notions of equality? Suppose we make comparisons with
>>>>> NaN trap:
>>>>>
>>>>> // Programmer fails to consider NaN behavior. This now traps if a
>>>>> or b is NaN.
>>>>> // That’s somewhat safer, but almost surely not the desired
>>>>> behavior.
>>>>> if a == b {
>>>>> }
>>>>>
>>>>> // Programmer considers NaNs. They now cannot use `==` until they
>>>>> rule out
>>>>> // either a or b is NaN. This actually makes the code *more*
>>>>> complicated and
>>>>> // less readable. Alternatively, they use `&==` or whatever we call
>>>>> the unsafe
>>>>> // comparison and it’s just like what we had before, except now
>>>>> they have a
>>>>> // “weird operator”.
>>>>> if (!a.isNaN && !b.isNaN && a == b) || (a.isNaN && b.isNaN) {
>>>>> }
>>>>>
>>>>> Now what happens if we return Bool?
>>>>>
>>>>> // Programmer fails to consider NaN behavior. Maybe the error when
>>>>> they
>>>>> // wrote a == b clues them in that they should. Otherwise they just
>>>>> throw in
>>>>> // a `!` and move on. They have the same bug they had before.
>>>>> if (a == b)! {
>>>>> }
>>>>>
>>>>> // Programmer considers NaNs. Unchanged from what we have currently,
>>>>> // except that we replace || with ??.
>>>>> if a == b ?? (a.isNaN && b.isNaN) {
>>>>> }
>>>>>
>>>>> If we are going to do the work of introducing another notion of
>>>>> floating-point equality, it should directly solve non-reflexivity of
>>>>> equality *by making equality reflexive*. My preferred approach would be to
>>>>> simply identify all NaNs:
>>>>>
>>>>> // Programmer fails to consider NaN behavior. Now their code works!
>>>>> if a == b {
>>>>> }
>>>>>
>>>>> // Programmer thinks about NaNs, realizes they can simplify their
>>>>> existing code:
>>>>> if a == b {
>>>>> }
>>>>>
>>>>> What are the downsides of this?
>>>>>
>>>>> (a) it will confuse sometimes experts who expect IEEE 754 semantics.
>>>>> (b) any code that uses `a != a` as an idiom for detecting NaNs will
>>>>> be broken.
>>>>>
>>>>> (b) is by far the bigger risk. It *will* result in some bugs.
>>>>> Hopefully less than result from people failing to consider NaNs. The only
>>>>> real risk with (a) is that we get a biennial rant posted to hacker news
>>>>> about Swift equality being broken, and the response is basically “read the
>>>>> docs, use &== if you want that behavior”.
>>>>>
>>>>>
>>>>> One more thought — and it’s crazy enough that I’m not even sure it’s
>>>>> worth posting — does Swift’s `Equatable` semantics require that `(a == b)
>>>>> != (a != b)` *always* evaluate to `true`?
>>>>>
>>>>
>>>> Yes. `!=` is an extension method that cannot be overridden
>>>>
>>>>
>>>> Wait, what? So if I have a `Password` type, and want to trigger extra
>>>> logging if the `!=` function is called too many times within a second or
>>>> something, that won't get called in generic code? That seems...
>>>> unintuitive...
>>>>
>>>
>>> That's correct, as it is for all protocol extension methods (for
>>> example, most of the collection APIs).
>>>
>>
>> Incidentally, even if it were desirable to log on comparison, why would
>> you want to log only if `!=` returns `true` and not when `==` returns
>> `false`?
>>
>>
>> Mostly because if I ever wrote a Password class, I’d probably make it
>> handle all the hashing and stuff internally so that the correct usage would
>> look like `if enteredPW != storedPW {...}`. I know they’re (generally) the
>> same, but I tend to think `_ != _` rather than `!(_ == _)`.
>>
>> I know it’d be a different thread (on a different mailing list), but does
>> anyone care if I propose that we change this? I’m strongly in favor of
>> providing a default implementation of !=, but I can’t think of why it
>> shouldn’t just be a *default* method... with the developers still having
>> the option to provide their own if they think they can get there faster
>> than the default implementation (or maybe even just because they want to
>> set a breakpoint on `!=` but not `==` or something).
>>
>
> You’d have to provide a convincing use case, but I’m not aware of one
> where evaluating “if x != y” should ever be different from “if !(x == y)”.
> Certainly, if you want != to log something, you would want == to log
> something. It is a feature, not a bug, that Swift guarantees that these are
> synonyms.
>
>
> Yes, but I might not want them to log the *same* thing... in particular
> (in this example), I'd want "==" to show up in one, and "!=" in the other.
> Mostly I'm just really surprised that we're swapping functions around such
> that any side effects might be different for generic vs concrete code. I
> guess another way to handle it would be to issue a warning when a type that
> conforms to a protocol implements a non-overrideable function of that
> protocol.
>
This has been raised in various forms over multiple years; any changes to
this area are very tricky to square with retroactive conformance.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-dev/attachments/20171101/7e912f42/attachment.html>
More information about the swift-dev
mailing list