[swift-dev] Rationalizing FloatingPoint conformance to Equatable

Xiaodi Wu xiaodi.wu at gmail.com
Tue Oct 31 22:58:36 CDT 2017


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`?
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-dev/attachments/20171031/1581414a/attachment.html>


More information about the swift-dev mailing list