> On Apr 24, 2017, at 10:54 PM, Xiaodi Wu <[email protected]> wrote: > > On Tue, Apr 25, 2017 at 12:52 AM, Jonathan Hull <[email protected] > <mailto:[email protected]>> wrote: > The only other alternative I can think of is to include a notion of unordered > to our comparisons… > > What if we made <=> return an optional comparison result, with nil meaning > unordered (which is how Ruby does it)? > > Have you seen my Gist? It's a whole design based on that idea.
The main difference is that I am suggesting using <=> vs. < and == as the different spellings for our different expectations. There would also be no trapping. < and == would always somehow represent a strict total ordering. <=> would adhere to the letter of 754 (including NaN being unordered… which is different than just unequal). > 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). > > As I say above, nan == nan is a non-starter for the numerics crowd, and I > have to agree. NaN *must not be equal to* NaN, or all sorts of numerics > recipes are likely to break in weird ways when ported to Swift. The numerics crowd should use <=>. You made a very convincing case before when talking about &+ that we already can’t expect direct cut and paste with C code. Worst case, I suppose we could have < and == use the current definitions. It isn’t ideal, but then nothing about this entire subject is ideal… Thanks, Jon > >> On Apr 24, 2017, at 8:25 PM, Xiaodi Wu <[email protected] >> <mailto:[email protected]>> wrote: >> >> On Mon, Apr 24, 2017 at 9:06 PM, Jonathan Hull via swift-evolution >> <[email protected] <mailto:[email protected]>> wrote: >> As I am thinking about it more, this means that for == and < >> >> NaN == NaN >> -0 == +0 >> +Inf < NaN >> >> Since this would break from IEEE, >> >> 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. >> >> 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. >> >> 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. >> >> 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... >> >> Thanks, >> Jon >> >> >> >> > On Apr 23, 2017, at 7:18 AM, Jonathan Hull via swift-evolution >> > <[email protected] <mailto:[email protected]>> wrote: >> > >> > There is one more option which hasn’t really been considered: >> > >> > • == and != are tied to the Equatable protocol, which is essentially the >> > == operation. >> > • <, <=, >, >= are tied to the Comparable protocol (which is kept the >> > same except for minor changes/additions listed below) >> > • Hashable still requires Equatable >> > • There is a new ComparisonMetric concept which lets an algorithm specify >> > exactly how the comparison is done (see below) >> > >> > >> > 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. >> > >> > 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. >> > >> > >> > ====The Design==== >> > // (Note: I wrote this code in mail, so it may not compile) >> > >> > >> > //This defines the result of a comparison. It would ideally be nested in >> > the protocol below if that becomes possible. >> > enum ComparisonResult : Int { >> > case ascending = -1 >> > case equal = 0 >> > case descending = 1 >> > } >> > >> > protocol Comparable { >> > typealias Metric = (Self, Self) -> ComparisonResult //Give >> > ourselves an easy way to refer to this function type >> > >> > var defaultMetric: Metric >> > static func <(lhs: Self, rhs: Self) -> Bool >> > } >> > >> > extension Comparable { >> > //Not shown: We would define <=, etc… plus ≤,≥,and ≠ (because, hey, >> > it is my proposal) >> > >> > func compare(with other: Self, using metric: Metric) -> >> > ComparisonResult { >> > return metric(self, other) >> > } >> > >> > func compare(with other: Self) -> ComparisonResult { >> > return self.defaultMetric(self, other) >> > } >> > >> > static func <=> (lhs: Self, rhs: Self) -> Int { >> > return self.defaultMetric(lhs, rhs).rawValue >> > } >> > >> > var defaultMetric: Metric { >> > return { lhs, rhs in >> > if lhs == rhs { >> > return .equal >> > } else if lhs < rhs { >> > return .ascending >> > } >> > return .descending >> > } >> > } >> > } >> > >> > ============ >> > >> > Then for Double, we would make a second metric for IEEE compliant (or >> > multiple for different levels) >> > >> > extension Double : Comparable { >> > >> > static func < (lhs: Self, rhs: Self) -> Bool { >> > //define using best for dictionaries / sets / layman >> > understanding >> > } >> > >> > static func == (lhs: Self, rhs: Self) -> Bool { >> > //define using best for dictionaries / sets / layman >> > understanding >> > } >> > >> > static var IEEEcompare:Comparable.Metric { >> > //return function here that does full IEEE comparison >> > } >> > >> > } >> > >> > Then we can call ‘myDouble.compare(with: otherDouble, using: >> > .IEEEcompare)’ when needed. >> > >> > >> > Thanks, >> > Jon >> > >> > >> > >> >> On Apr 22, 2017, at 9:58 PM, Chris Lattner via swift-evolution >> >> <[email protected] <mailto:[email protected]>> wrote: >> >> >> >> On Apr 22, 2017, at 6:06 PM, Xiaodi Wu <[email protected] >> >> <mailto:[email protected]>> wrote: >> >>> 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. >> >>> >> >>> 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. >> >>> >> >>> 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. >> >> >> >> >> >> 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: >> >> >> >> 1. The strictly correct but user hostile approach: >> >> >> >> * == and != are tied to the Equatable protocol, which is essentially the >> >> == operation. >> >> * <, <=, >, >= are tied to the Comparable protocol, which is essentially >> >> the <=> operation. >> >> * Hashable doesn’t require equatable, it requires a related >> >> StrictlyEquatable protocol. >> >> * 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 >> >> >> >> => This approach sucks because you can’t have Set<Float>, or >> >> Dictionary<Float, String>. >> >> >> >> 2. The strictly correct but somewhat user hostile approach: >> >> >> >> * == and != are tied to the Equatable protocol, which is essentially the >> >> == operation. >> >> * <, <=, >, >= are tied to the Comparable protocol, which is essentially >> >> the <=> operation. >> >> * Hashable doesn’t require equatable, it requires a related >> >> StrictlyEquatable protocol. >> >> * StrictlyEquatable doesn’t refine Equatable: it has a different >> >> requirement, and FP types can therefore implement both Equatable and >> >> StrictlyEquatable. >> >> >> >> => This approach is suboptimal because implementing your own type >> >> requires you to implement the <=> operation, as well as the >> >> StrictlyEquatable protocol, both. >> >> >> >> 3. The user friendly but incorrect model: >> >> >> >> * == and != are tied to the Equatable protocol, which is essentially the >> >> == operation. >> >> * <, <=, >, >= are tied to the Comparable protocol, which is essentially >> >> the <=> operation. >> >> * Hashable is defined in terms of Equatable. >> >> >> >> => This is easy (types just have to define <=>), but fails for FP types. >> >> >> >> >> >> 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: >> >> >> >> func doThing(a : Double, b : Double) -> Bool { >> >> …. >> >> return a != b >> >> } >> >> >> >> to: >> >> >> >> func doThing<T : FloatingPoint> (a : T, b : T) -> Bool { >> >> …. >> >> return a != b >> >> } >> >> >> >> would change behavior (e.g. when a is -0.0 and b is +0.0). Likewise, "T >> >> : Equatable". >> >> >> >> -Chris >> >> >> >> >> >> _______________________________________________ >> >> swift-evolution mailing list >> >> [email protected] <mailto:[email protected]> >> >> https://lists.swift.org/mailman/listinfo/swift-evolution >> >> <https://lists.swift.org/mailman/listinfo/swift-evolution> >> > >> > _______________________________________________ >> > swift-evolution mailing list >> > [email protected] <mailto:[email protected]> >> > https://lists.swift.org/mailman/listinfo/swift-evolution >> > <https://lists.swift.org/mailman/listinfo/swift-evolution> >> >> _______________________________________________ >> swift-evolution mailing list >> [email protected] <mailto:[email protected]> >> https://lists.swift.org/mailman/listinfo/swift-evolution >> <https://lists.swift.org/mailman/listinfo/swift-evolution> >
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
