[swift-evolution] [pitch] Comparison Reform
Xiaodi Wu
xiaodi.wu at gmail.com
Sat Apr 22 17:55:47 CDT 2017
On Sat, Apr 22, 2017 at 5:51 PM, Dave Abrahams <dabrahams at apple.com> wrote:
>
> on Sat Apr 22 2017, Xiaodi Wu <xiaodi.wu-AT-gmail.com> wrote:
>
> > 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
> > ```
>
> Oh, that's an interesting approach. Now you are asking people to
> translate the == in numeric code.
Yes. Specifically, in floating point code. I guess that's the part about
shaping the rug not to cover the bump. IEEE does not require `==` to be the
spelling of the quiet equality comparison operator, and it does
specifically describe a comparison operator that behaves identically to
what I propose as the trapping `==` (which, I believe, is actually spelled
in the IEEE standard as `==`).
I guess I'd want to hear what Steve
> Canon has to say about that.
>
> It still begs all the questions I've asked above, though. Should I
> repeat them?
>
> >> > 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
> >>
>
> --
> -Dave
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170422/4e537125/attachment.html>
More information about the swift-evolution
mailing list