<html><head><meta http-equiv="Content-Type" content="text/html charset=utf-8"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><br class=""><div><blockquote type="cite" class=""><div class="">On Apr 24, 2017, at 11:52 PM, Jonathan Hull via swift-evolution <<a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a>> wrote:</div><br class="Apple-interchange-newline"><div class=""><meta http-equiv="Content-Type" content="text/html charset=utf-8" class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class="">The only other alternative I can think of is to include a notion of unordered to our comparisons…</div><div class=""><br class=""></div><div class="">What if we made <=> return an optional comparison result, with nil meaning unordered (which is how Ruby does it)? For == and <, we still require a strict total ordering (i.e. they would not be optional). Most of the time, <=> and <, == would have the same comparison (<=> could have a default implementation based on == and <), but in the case of floating point it would be slightly different: <=> would return nil for anything involving NaN (in full accordance with IEEE 754), while < and == would somehow jam it into a strict total order (preferably with Nan == NaN so we don’t break collections).</div></div></div></blockquote><div><br class=""></div>There are many potential NaN values; in addition to signalling/quiet, there are payload bits. There could be more than a single unordered value.</div><div><br class=""></div><div><=> returning an optional enum might be appropriate, but there are still likely algorithms that would be written assuming that the operators defined in terms of comparable provide strict total ordering behavior:</div><div><span style="background-color: rgb(255, 255, 255);" class=""><br class=""></span></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;" class=""><div><span style="background-color: rgb(255, 255, 255);" class="">1.</span><span style="font-family: Menlo; font-size: 11px; background-color: rgb(255, 255, 255);" class=""> [3.0, 1.0, Float.nan, 2.0].sorted(by: <) </span></div><div><div style="margin: 0px; font-size: 11px; line-height: normal; font-family: Menlo; color: rgb(51, 187, 200); background-color: rgb(255, 255, 255);" class=""><span style="font-variant-ligatures: no-common-ligatures" class="">$R8: [Float] = 4 values {</span></div></div><div><div style="margin: 0px; font-size: 11px; line-height: normal; font-family: Menlo; color: rgb(51, 187, 200); background-color: rgb(255, 255, 255);" class=""><span style="font-variant-ligatures: no-common-ligatures" class=""> [0] = 1</span></div></div><div><div style="margin: 0px; font-size: 11px; line-height: normal; font-family: Menlo; color: rgb(51, 187, 200); background-color: rgb(255, 255, 255);" class=""><span style="font-variant-ligatures: no-common-ligatures" class=""> [1] = 3</span></div></div><div><div style="margin: 0px; font-size: 11px; line-height: normal; font-family: Menlo; color: rgb(51, 187, 200); background-color: rgb(255, 255, 255);" class=""><span style="font-variant-ligatures: no-common-ligatures" class=""> [2] = NaN</span></div></div><div><div style="margin: 0px; font-size: 11px; line-height: normal; font-family: Menlo; color: rgb(51, 187, 200); background-color: rgb(255, 255, 255);" class=""><span style="font-variant-ligatures: no-common-ligatures" class=""> [3] = 2</span></div></div></blockquote><div><br class=""></div><div>-DW</div><div><blockquote type="cite" class=""><div class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class=""><br class=""></div><div class="">Thanks,</div><div class="">Jon</div><br class=""><div class=""><blockquote type="cite" class=""><div class="">On Apr 24, 2017, at 8:25 PM, Xiaodi Wu <<a href="mailto:xiaodi.wu@gmail.com" class="">xiaodi.wu@gmail.com</a>> wrote:</div><br class="Apple-interchange-newline"><div class=""><div dir="ltr" style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class="">On Mon, Apr 24, 2017 at 9:06 PM, Jonathan Hull via swift-evolution<span class="Apple-converted-space"> </span><span dir="ltr" class=""><<a href="mailto:swift-evolution@swift.org" target="_blank" class="">swift-evolution@swift.org</a>></span><span class="Apple-converted-space"> </span>wrote:<br class=""><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;">As I am thinking about it more, this means that for == and <<br class=""><br class="">NaN == NaN<br class="">-0 == +0<br class="">+Inf < NaN<br class=""><br class="">Since this would break from IEEE,</blockquote><div class=""><br class=""></div><div class="">Yeah, as Steve mentioned, it's a huge deal to break from IEEE rules. Both the existing design and Dave's proposed design jump through huge hoops to preserve the behavior of these operators and reconcile them with the rest of Swift. The biggest weakness of my alternative idea as written up earlier is that it cannot preserve the spelling of these operators but requires a minor adjustment (`&`). It's simply a no-go to move `==` to `compare(with: other, using: .ieee)`. No one will write numerics algorithms like that.</div><div class=""><br class=""></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;">I think we should also consider taking the opportunity to make == and < work with a default tolerance. That is, 'a == b' would check that (a - b) < epsilon for some reasonable choice of epsilon that works for common usage. I know this would make the hash function harder to write, but I think it is worthwhile.<br class=""><br class="">Then, as I mentioned before, people who need strict IEEE conformance would explicitly declare that need by using 'compare(with: other, using: .IEEE)' or whatever we want to call that metric. We can even provide metrics for different IEEE levels, if desired.<br class=""><br class="">We could also provide a function ‘tolerance(_:Self) -> Comparable.Metric’ on FloatingPoint which returns a comparison metric that defines equality as (a - b) < tolerance, for those who want to specify a specific tolerance for their use-case/algorithm...<br class=""><br class="">Thanks,<br class="">Jon<br class=""><br class=""><br class=""><br class="">> On Apr 23, 2017, at 7:18 AM, Jonathan Hull via swift-evolution <<a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a>> wrote:<br class="">><br class="">> There is one more option which hasn’t really been considered:<br class="">><br class="">> • == and != are tied to the Equatable protocol, which is essentially the == operation.<br class="">> • <, <=, >, >= are tied to the Comparable protocol (which is kept the same except for minor changes/additions listed below)<br class="">> • Hashable still requires Equatable<br class="">> • There is a new ComparisonMetric concept which lets an algorithm specify exactly how the comparison is done (see below)<br class="">><br class="">><br class="">> Tl;dr: There are different definitions of ‘comparison’ which make sense in different domains… so let’s make it explicit so it doesn’t surprise anyone.<br class="">><br class="">> The question then becomes, which metric should be the default (i.e. the one defined by ‘<‘ and ‘==‘), and the answer is: the one which lets us use floats/doubles in dictionaries and sets. People and algorithms which need full IEEE correctness can use a different metric which specifically guarantees it. They can even build their own metric if needed.<br class="">><br class="">><br class="">> ====The Design====<br class="">> // (Note: I wrote this code in mail, so it may not compile)<br class="">><br class="">><br class="">> //This defines the result of a comparison. It would ideally be nested in the protocol below if that becomes possible.<br class="">> enum ComparisonResult : Int {<br class="">> case ascending = -1<br class="">> case equal = 0<br class="">> case descending = 1<br class="">> }<br class="">><br class="">> protocol Comparable {<br class="">> typealias Metric = (Self, Self) -> ComparisonResult //Give ourselves an easy way to refer to this function type<br class="">><br class="">> var defaultMetric: Metric<br class="">> static func <(lhs: Self, rhs: Self) -> Bool<br class="">> }<br class="">><br class="">> extension Comparable {<br class="">> //Not shown: We would define <=, etc… plus ≤,≥,and ≠ (because, hey, it is my proposal)<br class="">><br class="">> func compare(with other: Self, using metric: Metric) -> ComparisonResult {<br class="">> return metric(self, other)<br class="">> }<br class="">><br class="">> func compare(with other: Self) -> ComparisonResult {<br class="">> return self.defaultMetric(self, other)<br class="">> }<br class="">><br class="">> static func <=> (lhs: Self, rhs: Self) -> Int {<br class="">> return self.defaultMetric(lhs, rhs).rawValue<br class="">> }<br class="">><br class="">> var defaultMetric: Metric {<br class="">> return { lhs, rhs in<br class="">> if lhs == rhs {<br class="">> return .equal<br class="">> } else if lhs < rhs {<br class="">> return .ascending<br class="">> }<br class="">> return .descending<br class="">> }<br class="">> }<br class="">> }<br class="">><br class="">> ============<br class="">><br class="">> Then for Double, we would make a second metric for IEEE compliant (or multiple for different levels)<br class="">><br class="">> extension Double : Comparable {<br class="">><br class="">> static func < (lhs: Self, rhs: Self) -> Bool {<br class="">> //define using best for dictionaries / sets / layman understanding<br class="">> }<br class="">><br class="">> static func == (lhs: Self, rhs: Self) -> Bool {<br class="">> //define using best for dictionaries / sets / layman understanding<br class="">> }<br class="">><br class="">> static var IEEEcompare:Comparable.Metric {<br class="">> //return function here that does full IEEE comparison<br class="">> }<br class="">><br class="">> }<br class="">><br class="">> Then we can call ‘myDouble.compare(with: otherDouble, using: .IEEEcompare)’ when needed.<br class="">><br class="">><br class="">> Thanks,<br class="">> Jon<br class="">><br class="">><br class="">><br class="">>> On Apr 22, 2017, at 9:58 PM, Chris Lattner via swift-evolution <<a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a>> wrote:<br class="">>><br class="">>> On Apr 22, 2017, at 6:06 PM, Xiaodi Wu <<a href="mailto:xiaodi.wu@gmail.com" class="">xiaodi.wu@gmail.com</a>> wrote:<br class="">>>> but my quick reaction to `&==` is that it would make me quite nervous to have `==` not bound to 754-equals as it is in essentially every other language. In particular, I worry about the risk of people porting numerical code that depends on isnan(x) <—> !(x < y) in non-obvious ways that they are unlikely to test. I’ll try to follow up with more detailed thoughts tomorrow.<br class="">>>><br class="">>>> Indeed, it makes me a little nervous too. That said, `==` being either bound or not bound to 754 depending on the context is what makes me even more nervous.<br class="">>>><br class="">>>> I was once adamantly against a new spelling for `==`, but on reconsideration it's clear to me that few if any numerical recipes can be ported verbatim from C-like languages and we should probably not encourage people to do so. Already, `+` needs to be rewritten as `&+`, `<<` probably should be rewritten as `&<<` (I still haven't had enough time to think about this), and the bitwise operators have differing precedences that require careful proofreading.<br class="">>><br class="">>><br class="">>> I haven’t been following this proposal or discussion closely, but it seems to me that there are a few workable approaches with different tradeoffs:<br class="">>><br class="">>> 1. The strictly correct but user hostile approach:<br class="">>><br class="">>> * == and != are tied to the Equatable protocol, which is essentially the == operation.<br class="">>> * <, <=, >, >= are tied to the Comparable protocol, which is essentially the <=> operation.<br class="">>> * Hashable doesn’t require equatable, it requires a related StrictlyEquatable protocol.<br class="">>> * StrictlyEquatable refines Equatable (with no other requirements, it is just a marker protocol), in which case FP types can’t conform to it, and thus can’t participate as dictionary keys<br class="">>><br class="">>> => This approach sucks because you can’t have Set<Float>, or Dictionary<Float, String>.<br class="">>><br class="">>> 2. The strictly correct but somewhat user hostile approach:<br class="">>><br class="">>> * == and != are tied to the Equatable protocol, which is essentially the == operation.<br class="">>> * <, <=, >, >= are tied to the Comparable protocol, which is essentially the <=> operation.<br class="">>> * Hashable doesn’t require equatable, it requires a related StrictlyEquatable protocol.<br class="">>> * StrictlyEquatable doesn’t refine Equatable: it has a different requirement, and FP types can therefore implement both Equatable and StrictlyEquatable.<br class="">>><br class="">>> => This approach is suboptimal because implementing your own type requires you to implement the <=> operation, as well as the StrictlyEquatable protocol, both.<br class="">>><br class="">>> 3. The user friendly but incorrect model:<br class="">>><br class="">>> * == and != are tied to the Equatable protocol, which is essentially the == operation.<br class="">>> * <, <=, >, >= are tied to the Comparable protocol, which is essentially the <=> operation.<br class="">>> * Hashable is defined in terms of Equatable.<br class="">>><br class="">>> => This is easy (types just have to define <=>), but fails for FP types.<br class=""><span class="">>><br class="">>><br class="">>> I don’t think that this proposal is acceptable as written. I think it is really bad that abstracting a concrete algorithm would change its behavior so substantially. I don’t care about SNaNs, but I do care about the difference between +0/-1 and secondarily that of NaN handling. It seems really bad that generalizing something like:<br class="">>><br class="">>> func doThing(a : Double, b : Double) -> Bool {<br class="">>> ….<br class="">>> return a != b<br class="">>> }<br class="">>><br class="">>> to:<br class="">>><br class="">>> func doThing<T : FloatingPoint> (a : T, b : T) -> Bool {<br class="">>> ….<br class="">>> return a != b<br class="">>> }<br class="">>><br class="">>> would change behavior (e.g. when a is -0.0 and b is +0.0). Likewise, "T : Equatable".<br class="">>><br class=""></span>>> -Chris<br class="">>><br class="">>><br class="">>> ______________________________<wbr class="">_________________<br class="">>> swift-evolution mailing list<br class="">>><span class="Apple-converted-space"> </span><a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a><br class="">>><span class="Apple-converted-space"> </span><a href="https://lists.swift.org/mailman/listinfo/swift-evolution" rel="noreferrer" target="_blank" class="">https://lists.swift.org/<wbr class="">mailman/listinfo/swift-<wbr class="">evolution</a><br class="">><br class="">> ______________________________<wbr class="">_________________<br class="">> swift-evolution mailing list<br class="">><span class="Apple-converted-space"> </span><a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a><br class="">><span class="Apple-converted-space"> </span><a href="https://lists.swift.org/mailman/listinfo/swift-evolution" rel="noreferrer" target="_blank" class="">https://lists.swift.org/<wbr class="">mailman/listinfo/swift-<wbr class="">evolution</a><br class=""><br class="">______________________________<wbr class="">_________________<br class="">swift-evolution mailing list<br class=""><a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a><br class=""><a href="https://lists.swift.org/mailman/listinfo/swift-evolution" rel="noreferrer" target="_blank" class="">https://lists.swift.org/<wbr class="">mailman/listinfo/swift-<wbr class="">evolution</a></blockquote></div></div></div></div></blockquote></div><br class=""></div>_______________________________________________<br class="">swift-evolution mailing list<br class=""><a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a><br class="">https://lists.swift.org/mailman/listinfo/swift-evolution<br class=""></div></blockquote></div><br class=""></body></html>