<div dir="ltr">On Sat, Apr 15, 2017 at 10:32 PM, Dave Abrahams via swift-evolution <span dir="ltr"><<a href="mailto:swift-evolution@swift.org" target="_blank">swift-evolution@swift.org</a>></span> wrote:<br><div class="gmail_extra"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><span class="gmail-"><br>
on Sat Apr 15 2017, Xiaodi Wu <<a href="mailto:swift-evolution@swift.org">swift-evolution@swift.org</a>> wrote:<br>
<br>
> On Sat, Apr 15, 2017 at 3:12 PM, Dave Abrahams via swift-evolution <<br>
> <a href="mailto:swift-evolution@swift.org">swift-evolution@swift.org</a>> wrote:<br>
><br>
>><br>
>> on Thu Apr 13 2017, Xiaodi Wu <<a href="mailto:swift-evolution@swift.org">swift-evolution@swift.org</a>> wrote:<br>
>><br>
>> > Getting this sorted out is definitely a worthwhile effort. I do have<br>
>> > thoughts about this proposal:<br>
>> ><br>
>> > I continue to have reservations about an identical spelling (e.g. `==`)<br>
>> > giving two different answers with the same values of the same type,<br>
>> > depending on the generic context. It is a very *clever* design, but it is<br>
>> > also a very *subtle* behavior that I can see leading to much confusion<br>
>> and<br>
>> > befuddlement for any user who is not well versed *both* in the<br>
>> intricacies<br>
>> > of IEEE floating point *and* in the intricacies of Swift.<br>
>><br>
>> I can't help but think that the concern over confusion here is not<br>
>> informed by any realistic situations. Please describe<br>
>><br>
><br>
> To be clear, I'm not claiming that my concerns about the proposal outweigh<br>
> my enthusiasm for it.<br>
><br>
> But here, the confusion I'm concerned about stems from the essential<br>
> conclusion by the proposal authors that types (including, but not<br>
> necessarily only limited to FP types) which are ordinarily compared in a<br>
> way that treats certain "special values" differently must also present an<br>
> alternative notion of comparison that accounts for all possible<br>
> values.<br>
<br>
</span>That may be a conclusion, but it's not an assumption. For example, it's<br>
totally reasonable that there is a value of Int (i.e. 0) for which the<br>
requirements of division don't hold. We say that 0 is outside the<br>
domain of / when used as a divisor, and we tried to get away with saying<br>
that NaN was outside the domain of ==. However, it's also reasonable to<br>
trap on integer division by zero.<br>
<br>
What we have is a situation where values that “misbehave” when given<br>
IEEE semantics occur in normal code and are expected to interoperate<br>
with other floating point values under normal circumstances (such as<br>
when sorting), and not only interoperate but give reasonable results.<br>
<br>
Now, having thought about this a bit more myself, here is a real case<br>
where confusion might occur:<br>
<br>
if values.contains(.NaN) {<br>
print(values.filter { $0 != .NaN }) // Surprise, NaN is printed!<br>
}<br>
<br>
I find this result truly loathsome, but it seems to me that the only<br>
reasonable cure is giving == equivalence relation semantics under all<br>
circumstances.<br>
<span class="gmail-"><br>
> The special casing of certain values already makes comparison<br>
> operations difficult to understand; I guess I'm simply stating what is<br>
> unavoidably true, that having a "non-special-cased" comparison *in<br>
> addition* to that adds additional difficulty.<br>
<br>
</span>Yup.<br>
<span class="gmail-"><br>
> One example where this comes to mind is this: `XCTAssertEqual(+0.0, -0.0)`<br>
> will pass or fail depending on whether XCTAssertEqual provides a<br>
> FP-specific specialization. This is something that cannot be determined by<br>
> reading the testing code itself: that is, it requires not merely knowledge<br>
> about whether or not the call site "knows" that it's operating on a FP<br>
> type, but also knowledge about whether XCTest itself is FP-aware.<br>
<br>
</span>Yep.<br>
<span class="gmail-"><br>
> The issue increases in difficulty when it comes to non-stdlib types.<br>
> Suppose I were to write a Rational type. (I am writing a Rational type, but<br>
> we can talk about that later.) This Rational type is capable of<br>
> representing NaN (`(0 / 0 as Rational<Int>).isNaN == true`) and, for<br>
> consistency with FP types, `Rational<Int>.nan != Rational<Int>.nan`.<br>
<br>
</span>Hey, it's your funeral! ;-)<br>
<span class="gmail-"><br>
> If this proposal is implemented, *I* would be responsible for making<br>
> XCTest Rational-aware, vending `XCTAssertEqual<Rational<T>>(_<wbr>:_:)`.<br>
<br>
</span>Now you're mixing apples and oranges with NaN and +/-0, the latter of<br>
which doesn't apply to rationals. The way you make XCTest aware of NaN<br>
is to do the same thing that floating point did: vend the static version<br>
of == separately from the dynamic version, using @_implements (and get<br>
us to drop the underscore).<br>
<br>
XCTestAssertEqual is a perfect example of a generic function: it is (or<br>
should be!) concerned with verifying that results match expectations,<br>
not that “==” behaves according to the specific quirks of a type.<br>
Anything else would make<br>
<br>
XCTestAssertEqual(<wbr>resultOfComputation, NaN)<br>
<br>
meaningless.</blockquote><div><br></div><div>Hmm, I will admit I have not tried it because I have always assumed that the assertion above is meaningless, and that it ought to be. `XCTAssertTrue(resultOfComputation.isNaN)` is the way I've been testing my NaN's.</div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><span class="gmail-">
> In fact, to ensure that users of Rational get the expected behavior<br>
> everywhere, I would have to vend special Rational-aware versions of<br>
> every stdlib, core library, and _third-party_ function that is<br>
> FP-aware, which it is not possible to do.<br>
<br>
</span>I'm sorry, I don't follow. Give me an example, please, and also clearly<br>
define “FP-awareness.”<br></blockquote><div><br></div><div>By FP awareness, I mean that in comparing two FP values a function uses `FloatingPoint.==` as opposed to `Comparable.==`. 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.</div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><span class="gmail-">
>> Actually, the fact that this behavior cannot even be achieved without<br>
>> > currently non-existent compiler features means that it is not possible<br>
>> > to understand what's truly going on without reading *this document*,<br>
>><br>
>> This doesn't seem like a reasonable argument. New compiler features get<br>
>> documented outside of the proposals they come from. Nobody's going to<br>
>> have to go read a proposal to understand what @implements means.<br>
><br>
> I do not mean that I seriously believe @_implements will never be<br>
> documented. (Although, as an underscored attribute, it does not need to be<br>
> documented outside of the apple/swift repo, in the same way that<br>
> @_transparent and its ilk are not documented outside the repo.)<br>
<br>
</span>Either way, we either end up documenting @_implements or special magic<br>
behavior for floating point types. Somebody already needs to document<br>
the latter for floating point types (.NaN != .NaN), frankly, even though IEEE is a<br>
commonly implemented standard.<br>
<span class="gmail-"><br>
> I am simply saying that fully understanding how `Comparable` and<br>
> `FloatingPoint` interact with each other will require learning one<br>
> additional advanced feature of the language than is required now,<br>
<br>
</span>Agreed, it will require learning something new.<br>
<span class="gmail-"><br>
> and that this feature does not even currently exist.<br>
<br>
</span>I don't understand the relevance of that last point though.</blockquote><div><br></div><div>Not critical to the argument. Just that it will impact all users of Swift. It's not as though some particularly studious user of Swift 3 will be rewarded with a head start because he or she has devoted time to careful study of the language.</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><span class="gmail-"><br>
><br>
><br>
>> > after mastering *both* IEEE floating point *and* Swift<br>
>> > generics/protocols/extensions/<wbr>static vs. dynamic dispatch. All to use<br>
>> > `==` correctly.<br>
>><br>
>> I don't understand this argument. The *whole* point of this proposal is<br>
>> that you use `==` correctly *without* the need for any special knowledge.<br>
>><br>
>> > Which is to say, most people will simply not even know if they happen<br>
>> > to be using the `==` they did not intend to use.<br>
>><br>
>> Most people aren't aware that IEEE comparison is quirky and don't know<br>
>> what they intend with respect to those semantics, but anyone who *is*<br>
>> aware of his intention has an easy way to understand what's happening.<br>
>> Does this code know it's operating on floating point numbers? If so,<br>
>> it's IEEE. If not, it's an equivalence relation.<br>
>><br>
>> > I think consideration should be given to a design that achieves a<br>
>> > user-facing but not onerous differentiation between level 1 and level 2<br>
>> > equality. However, the only one I can think of is essentially a different<br>
>> > shade of the `PartiallyComparable` alternative already outlined in the<br>
>> > document.<br>
>><br>
>> I am *deeply* opposed to such a protocol. It is purely syntactic in<br>
>> nature and thus useless for correctly constraining generic algorithms.<br>
><br>
> But there do exist types for which some pairs of values cannot be compared<br>
> to each other. A generic algorithm that blows up if a value cannot be<br>
> compared to another value should not operate on such types, no?<br>
<br>
</span>I don't know what you mean by “blows up.” If you mean, “traps,” I think<br>
that's fine. If you mean, “misbehaves,” I think that's really hard to<br>
justify unless it's easy for users to keep these values from ever<br>
entering their program. The algorithm itself doesn't know all the types<br>
on which it can operate, so its documentation isn't in a position to<br>
warn users about this precondition. Therefore, the caller, who got the<br>
values from somewhere and may be generic itself, is in no position to<br>
check for the problem. The only cure is to bottleneck production of<br>
values of that type and ensure that those values never get beyond some<br>
boundary in the code. I don't think that's practical in the case of<br>
floating point, not least because ordinary operations on legit values<br>
can produce the problematic values.<br>
<span class="gmail-"><br>
>> People will use it anyway, resulting in algorithms that statically<br>
>> accept, but are incorrect for, floating point. In my opinion there's<br>
>> only one feasible answer that doesn't use the static/dynamic distinction<br>
>> we've proposed: throw IEEE semantics under the bus, making it available<br>
>> only under a different syntax.<br>
>><br>
>> This would be a drastic move whose primary downside is that floating<br>
>> point code ported from C would need to be carefully audited and may<br>
>> become less easy to read. But at least it would be viable.<br>
>><br>
><br>
> Yes I agree that it would be a much more drastic move that would make<br>
> Swift difficult to use for numerics, and I, at least, would not want<br>
> to go down that road.<br>
<br>
</span>I'd rather not, myself. The choices are not good:<br>
<br>
1. Complexity and occasional surprises that can be documented and<br>
explained.<br>
2. Silent misbehavior.<br>
3. Separate syntax for distinct semantics.<br>
<br>
#2 is the only one I find completely unacceptable.<br>
<div><div class="gmail-h5"><br>
>> > Yet I cannot help but think that the rejected alternative may be<br>
>> > advantageous in one key aspect. `FloatingPoint` comparison is in a<br>
>> > sense "less refined" (not exactly precise language, I know) than the<br>
>> > level 2 ordering proposed here, at least in the sense that the latter<br>
>> > offers more semantic guarantees about the relationships between<br>
>> > comparison operators.<br>
>><br>
>> No, they are effectively refinement siblings. If it was a hierarchical<br>
>> relationship, we could just make the floating point types conform to<br>
>> Equatable. But floating point types are *required* by IEEE to have<br>
>> comparison semantics that conflict with Equatable.<br>
>><br>
>> > It's weird that the less refined `FloatingPoint` refines the more<br>
>> > refined `Comparable`,<br>
>><br>
>> Do you want to be able to sort floating point numbers without providing<br>
>> a comparison predicate (one that has to be spelled less obviously than<br>
>> "<")? If so, floating point numbers must be Comparable. If not, we<br>
>> could talk about breaking this refinement relationship.<br>
>><br>
>> > and I think the acrobatics with compiler support illustrate how the<br>
>> > design is actively working against Swift's overarching direction.<br>
>><br>
>> It's not more acrobatic than things we already do in the standard<br>
>> library to ensure that people transparently see the right behavior (see<br>
>> .lazy), and we could probably even find ways to do it without language<br>
>> features. It would be less principled, harder to understand, and more<br>
>> fragile than designing a language feature that addresses the need<br>
>> directly.<br>
><br>
> I have an incipient idea. It begins with:<br>
><br>
> enum ComparisonResult {<br>
> case orderedAscending, orderedSame, orderedDescending, unordered<br>
> }<br>
><br>
> I am sure there is something I am missing which makes this design a<br>
> horrible idea, but I'm interested in hearing them.<br>
<br>
</div></div>It's not a horrible idea, but to be fair it's not really a design yet,<br>
either. You haven't said anything about what it's supposed to mean, how<br>
it is supposed to be used, how people write, use, and compose generic<br>
algorithms with it, how it deals with floating point, etc.<br></blockquote><div><br></div><div>I've evolved my thinking based on the discussion. Let's see:</div><div><br></div><div>```</div><div>public enum ComparisonResult : Equatable {</div><div> case orderedAscending, equivalent, orderedDescending, unordered</div><div> // I have renamed one case, in the hopes of emphasizing that two values</div><div> // that compare `equivalent` are not merely ordered the same, but should</div><div> // satisfy the three conditions of an equivalence relation.</div><div>}</div><div>// I will have to leave the question of how to bridge from Obj-C<br></div><div>// NSComparisonResult up to more capable hands.</div><div><br></div><div>public protocol Comparable : Equatable {</div><div> func compared(to other: Self) -> ComparisonResult</div><div>}</div><div>// This will have to be modified as necessarily to support compiler magic</div><div>// necessary for source-compatibility with Swift 3</div><div><br></div><div>extension Comparable {</div><div> public static func == (lhs: Self, rhs: Self) -> Bool {</div><div> let comparison = lhs.compared(to: rhs)</div><div><div> precondition(comparison != .unordered)</div><div> return comparison == .equivalent</div></div><div> }</div><div><br></div><div> public static func < (lhs: Self, rhs: Self) -> Bool {</div><div> let comparison = lhs.compared(to: rhs)</div><div> precondition(comparison != .unordered)</div><div> return comparison == .orderedAscending</div><div> }</div><div> // etc.</div><div><br></div><div> // Something I thought I'd never want to see, but on reflection not terrible:</div><div> public static func &== (lhs: Self, rhs: Self) -> Bool {</div><div> return lhs.compared(to: rhs) == .equivalent</div><div> }</div><div><br></div><div> public static func &< (lhs: Self, rhs: Self) -> Bool {</div><div> return lhs.compared(to: rhs) == .orderedAscending</div><div> }</div><div> // etc.</div><div>}</div><div><br></div><div>extension FloatingPoint : Comparable {</div><div> public func compared(to other: Self) -> ComparisonResult {</div><div> if isNaN || other.isNaN { return .unordered }<br></div><div> if isLess(than: other) { return .orderedAscending }</div><div> if other.isLess(than: self) { return .orderedDescending }</div><div><br></div><div><div> // On reconsideration, probably actually least surprising if +0.0 == -0.0</div><div> // no matter what. It does not matter that division can give different</div><div> // results for two equivalent values, only that the three rules of an</div><div> // equivalence relation will hold, and it does.</div><div> //</div><div> // If a user is really savvy to the sign of zero, there is FloatingPoint.sign,</div><div> // which is necessary in any case for working with FP operations that</div><div> // distinguish between +0 and -0. I can't think of a generic algorithm over</div><div> // all Comparable types that could make use of the distinction between +0</div><div> // and -0.</div></div><div> return .equivalent</div><div> }</div><div>}</div><div>```</div><div><br></div><div>In this design, `==`, `<`, etc. correspond to IEEE "signaling" operators, while `&==`, `&<`, etc. correspond to C-style operators, which IEEE calls "quiet" and actually notates using "?==", "?<", etc.</div><div><br></div></div></div></div>