[swift-dev] Rationalizing FloatingPoint conformance to Equatable
Xiaodi Wu
xiaodi.wu at gmail.com
Wed Nov 1 12:21:34 CDT 2017
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.
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-dev/attachments/20171101/add093b2/attachment.html>
More information about the swift-dev
mailing list