[swift-evolution] [pitch] Comparison Reform

Xiaodi Wu xiaodi.wu at gmail.com
Sat Apr 22 16:42:05 CDT 2017


On Sat, Apr 22, 2017 at 4:14 PM, Dave Abrahams <dabrahams at apple.com> wrote:

>
> on Tue Apr 18 2017, Xiaodi Wu <xiaodi.wu-AT-gmail.com> wrote:
>
> > On Tue, Apr 18, 2017 at 10:40 AM, Ben Cohen via swift-evolution <
> > swift-evolution at swift.org> wrote:
> >
> >>
> >> On Apr 17, 2017, at 9:40 PM, Chris Lattner via swift-evolution <
> >> swift-evolution at swift.org> wrote:
> >>
> >>
> >> On Apr 17, 2017, at 9:07 AM, Joe Groff via swift-evolution <
> >> swift-evolution at swift.org> wrote:
> >>
> >>
> >> On Apr 15, 2017, at 9:49 PM, Xiaodi Wu via swift-evolution <
> >> swift-evolution at swift.org> wrote:
> >>
> >> For example, I expect `XCTAssertEqual<T : FloatingPoint>(_:_:)` to be
> >> vended as part of XCTest, in order to make sure that `XCTAssertEqual(
> resultOfComputation,
> >> Double.nan)` always fails.
> >>
> >>
> >> Unit tests strike me as an example of where you really *don't* want
> level
> >> 1 comparison semantics. If I'm testing the output of an FP operation, I
> >> want to be able to test that it produces nan when I expect it to, or
> that
> >> it produces the right zero.
> >>
> >>
> >> I find it very concerning that == will have different results based on
> >> concrete vs generic type parameters.  This can only lead to significant
> >> confusion down the road.  I’m highly concerned about situations where
> >> taking a concrete algorithm and generalizing it (with generics) will
> change
> >> its behavior.
> >>
> >>
> >> It is already the case that you can start with a concrete algorithm,
> >> generalize it, and get confusing results – just with a different
> starting
> >> point. If you start with a concrete algorithm on Int, then generalize
> it to
> >> all Equatable types, then your algorithm will have unexpected behavior
> for
> >> floats, because these standard library types fail to follow the rules
> >> explicitly laid out for conforming to Equatable.
> >>
> >> This is bad. Developers need to be able to rely on those rules. The
> >> standard library certainly does:
> >>
> >> let a: [Double] = [(0/0)]
> >> var b = a
> >>
> >> // true, because fast path buffer pointer comparison:
> >> a == b
> >>
> >> b.reserveCapacity(10) // force a reallocation
> >>
> >> // now false, because memberwise comparison and nan != nan,
> >> // violating the reflexivity requirement of Equatable:
> >> a == b
> >>
> >>
> >> Maybe we could go through and special-case all the places in the
> standard
> >> library that rely on this, accounting for the floating point behavior
> >> (possibly reducing performance as a result). But we shouldn't expect
> users
> >> to.
> >>
> >
> > I was not thinking about the issue illustrated above, but this is
> > definitely problematic to me.
> >
> > To be clear, this proposal promises that `[0 / 0 as Double]` will be made
> > to compare unequal with itself, yes?
>
> Nope.
>
> As you know, equality of arrays is implemented generically and based on
> the equatable conformance of their elements.  Therefore, two arrays of
> equatable elements are equal iff the conforming implementation of
> Equatable's == is true for all elements.
>
> > It is very clear that here we are working with a concrete FP type and
> > not in a generic context, and thus all IEEE FP behavior should apply.
>
> I suppose that's one interpretation, but it's not the right one.
>
> If this were C++, it would be different, because of the way template
> instantiation works: in a generic context like the == of Array, the
> compiler would look up the syntactically-available == for the elements
> and use that.  But Swift is not like that; static lookup is done at the
> point where Array's == is compiled, and it only finds the == that's
> supplied by the Element's Equatable conformance.
>
> This may sound like an argument based on implementation details of the
> language, and to some extent it is.  But that is also the fundamental
> nature of the Swift language (and one for which we get many benefits),
> and it is hopeless to paper over it.  For example, I can claim that all
> doubles are equal to one another:
>
>   9> func == (lhs: Double, rhs: Double) -> Bool { return true }
>  10> 4.0 == 1.0
> $R2: Bool = true
>  11> [4.0] == [1.0]  // so the arrays should be equal too!
> $R3: Bool = false
>
> Another way to look at this is that Array is not a numeric vector, and
> won't be one no matter what you do ([1.0] + [2.0] => [1.0, 2.0]).  So it
> would be wrong for you to expect it to reflect the numeric properties of
> its elements.
>
> >> This is a bump in the rug – push it down in one place, it pops up in
> >> another. I feel like this proposal at least moves the bump to where
> fewer
> >> people will trip over it. I think it highly likely that the
> intersection of
> >> developers who understand enough about floating point to write truly
> >> correct concrete code, but won’t know about or discover the documented
> >> difference in generic code, is far smaller than the set of people who
> hit
> >> problems with the existing behavior.
> >>
> >
> > So, to extend this analogy, I'd rather say that the bump is not in the
> rug
> > [Comparable] but rather in a section of the floor [FP NaN]. The rug might
> > overlie the bump, but the bump will always be there and people will find
> it
> > as they walk even if they don't immediately see it.
>
> Correct.
>
> > If we don't want people to trip over the bump while walking on the
> > rug, one very good alternative, IMHO, is to shape the rug so that it
> > doesn't cover the bump.
>
> At what cost?
>
> More specifically: why is it the right behavior, for our audience, to
> trap when Equatable comparison happens to encounter NaN?  Will this not
> simply "crash" programs in the field that otherwise would have "just
> worked?"
>
> > My purpose in exploring an alternative design is to see if it would be
> > feasible for non-FP-aware comparison operators to refuse to compare NaN,
> > rather than giving different answers depending on context.
>
> So... to be clear, this is still different behavior based on context.
> Is this not just as confusing a result?
>
>   let nan = 0.0 / 0.0
>   print(nan == nan)     // false
>   print([nan] == [nan]) // trap
>

No, in my alternative proposal:

```
   let nan = 0.0 / 0.0
   print(nan == nan)     // trap
   print([nan] == [nan]) // trap
   print(nan &== nan) // false
   print([nan] &== [nan]) // false
```

> I now strongly believe that this may make for a design simultaneously
> > _less_ complex *and* _more_ comprehensive (as measured by the
> > flatness-of-rug metric).
>
> I'm certainly willing to discuss it, but so far it doesn't seem like
> you've been willing to answer the central questions above.
>
> --
> -Dave
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170422/689e7d9a/attachment.html>


More information about the swift-evolution mailing list