[swift-evolution] [pitch] Comparison Reform

Jaden Geller jaden.geller at gmail.com
Thu Apr 13 20:05:43 CDT 2017


> 
> On Apr 13, 2017, at 3:23 PM, John McCall via swift-evolution <swift-evolution at swift.org> wrote:
> 
>> On Apr 13, 2017, at 4:17 PM, Ben Cohen via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>> ComparisonResult Conveniences
>> 
>> There are a few conveniences we could consider providing to make ComparisonResult more ergonomic to manipulate. Such as:
>> 
>> // A way to combine orderings
>> func ComparisonResult.breakingTiesWith(_ order: () -> ComparisonResult) -> ComparisonResult
>> 
>> array.sort {
>>   $0.x.compare($0.y)
>>   .breakingTiesWith { $0.y.compare($1.y) }
>>   == .orderedAscending 
>> }
> 
> The really nice thing about compare being an operator is that you can very nicely combine it with an operator here and get a much more light-weight syntax for chained comparisons, e.g.:
> 
> struct MyPoint : Comparable {
>   var x, y, z: Double
>   func compare(_ other: MyPointer) -> ComparisonResult {
>     return self.x <=> other.x || self.y <=> other.y || self.z <=> other.z

Wow, this is elegant!

>   }
> }
> 
> as opposed to, I guess,
>   return self.x.compare(other.x).breakingTiesWith { self.y.compare(other.y).breakingTiesWith { self.z.compare(other.z) } }
> 
> But this is mostly useful for defining custom comparisons, so perhaps it's not worth having to paint a bikeshed for <=> and whatever the combining operator is.

For the record, I would strongly prefer `<=>` to an instance `compare` method. That said, I’d also prefer a static `compare` function to the asymmetric instance method if the spelling `compare` were absolutely desired.

It’s probably worth noting somewhere that an instance `compare` method performs dynamic dispatch on the left-hand argument while a static function (as well as the current operators `==` and `<`) perform static dispatch. I realize NSObject set a precedent with `isEqual:` and `compare:` instance methods, but I’m not convinced that’s necessarily the best design. If dynamic dispatch is desired, an implementation can always delegate to such a method.

> 
> Also, in this example:
>> // A way to combine orderings
>> func ComparisonResult.breakingTiesWith(_ order: () -> ComparisonResult) -> ComparisonResult
>> 
>> array.sort {
>>   $0.x.compare($0.y)
>>   .breakingTiesWith { $0.y.compare($1.y) }
>>   == .orderedAscending 
>> }
> Requiring this last "== .orderedAscending" seems like a tragic failure of ergonomics.  I understand that sorting doesn't actually require a tri-valued comparison, but is it really worth maintaining two currencies of comparison result over that?  Are there any types that can answer '<' substantially more efficiently than they can answer 'compare'?  And I assume this is related to why you've kept < in the protocol.

I would strongly support replacing (T, T) -> Bool with (T, T) -> ComparisonResult variants.

The areInIncreasingOrder variant is confusing at the call-site since the definition must be consulted to determine which order the comparison expects.
The areInIncreasingOrder implementation is very dirty when an == result is desired. This is especially bad if we expect other authors to mirror this API:
if !areInIncreasingOrder(a, b) && !areInIncreasingOrder(b, a) {
  // equal!
}
Not only is this unintuitive, but it is also less performant in many cases.

> 
> John.
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170413/3e291406/attachment.html>


More information about the swift-evolution mailing list