On Sat, Apr 22, 2017 at 4:14 PM, Dave Abrahams <dabrah...@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@swift.org> wrote: > > > >> > >> On Apr 17, 2017, at 9:40 PM, Chris Lattner via swift-evolution < > >> swift-evolution@swift.org> wrote: > >> > >> > >> On Apr 17, 2017, at 9:07 AM, Joe Groff via swift-evolution < > >> swift-evolution@swift.org> wrote: > >> > >> > >> On Apr 15, 2017, at 9:49 PM, Xiaodi Wu via swift-evolution < > >> swift-evolution@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 >
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution