on Sat Apr 22 2017, Xiaodi Wu <xiaodi.wu-AT-gmail.com> wrote: > 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 > ```
Oh, that's an interesting approach. Now you are asking people to translate the == in numeric code. 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 _______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution