[swift-evolution] [pitch] Comparison Reform

Karl Wagner razielim at gmail.com
Sun Apr 16 11:35:55 CDT 2017


> On 16 Apr 2017, at 05:32, Dave Abrahams via swift-evolution <swift-evolution at swift.org> wrote:
> 
> 
> on Sat Apr 15 2017, Xiaodi Wu <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
> 
>> On Sat, Apr 15, 2017 at 3:12 PM, Dave Abrahams via swift-evolution <
>> swift-evolution at swift.org> wrote:
>> 
>>> 
>>> on Thu Apr 13 2017, Xiaodi Wu <swift-evolution at swift.org> wrote:
>>> 
>>>> Getting this sorted out is definitely a worthwhile effort. I do have
>>>> thoughts about this proposal:
>>>> 
>>>> I continue to have reservations about an identical spelling (e.g. `==`)
>>>> giving two different answers with the same values of the same type,
>>>> depending on the generic context. It is a very *clever* design, but it is
>>>> also a very *subtle* behavior that I can see leading to much confusion
>>> and
>>>> befuddlement for any user who is not well versed *both* in the
>>> intricacies
>>>> of IEEE floating point *and* in the intricacies of Swift.
>>> 
>>> I can't help but think that the concern over confusion here is not
>>> informed by any realistic situations.  Please describe
>>> 
>> 
>> To be clear, I'm not claiming that my concerns about the proposal outweigh
>> my enthusiasm for it.
>> 
>> But here, the confusion I'm concerned about stems from the essential
>> conclusion by the proposal authors that types (including, but not
>> necessarily only limited to FP types) which are ordinarily compared in a
>> way that treats certain "special values" differently must also present an
>> alternative notion of comparison that accounts for all possible
>> values. 
> 
> That may be a conclusion, but it's not an assumption.  For example, it's
> totally reasonable that there is a value of Int (i.e. 0) for which the
> requirements of division don't hold.  We say that 0 is outside the
> domain of / when used as a divisor, and we tried to get away with saying
> that NaN was outside the domain of ==.  However, it's also reasonable to
> trap on integer division by zero.
> 
> What we have is a situation where values that “misbehave” when given
> IEEE semantics occur in normal code and are expected to interoperate
> with other floating point values under normal circumstances (such as
> when sorting), and not only interoperate but give reasonable results.
> 
> Now, having thought about this a bit more myself, here is a real case
> where confusion might occur:
> 
>  if values.contains(.NaN) {
>    print(values.filter { $0 != .NaN }) // Surprise, NaN is printed!
>  }
> 
> I find this result truly loathsome, but it seems to me that the only
> reasonable cure is giving == equivalence relation semantics under all
> circumstances.


The thing that’s bad about it is that we silently pick different operators for different contexts. With Swift’s heavy use of abstraction layering, you often can’t really tell what the context is (if it even has meaning at all).

I’ve been thinking about Swift architectural patterns, and one pattern that I think of as being a good fit for the language is this idea of wrapping operations on protocol-types as generic structs (for example, the way we do FilterCollection, LazyMapCollection, and he various String views in the standard library), providing transformed “views” of the object with a common base protocol (which will hopefully get optimised away). I wonder if we couldn’t apply a similar idea here…

So basically, every FloatingPoint will expose another pseudo-FloatingPoint type which differs from its base object only in its interpretation of “Comparable” operators. The type-system would enforce that you are comparing them consistently.

protocol FloatingPoint: Comparable {

    /// A FloatingPoint with comparison quirks
    ///
    associatedtype StandardComparable: FloatingPoint
    var comparable: StandardComparable { get }

    /// A FloatingPoint which compares according to IEEE level <whatever>
    ///
    associatedtype IEEEComparable: FloatingPoint = Self
    var ieeeComparable: IEEEComparable { get }
}
extension FloatingPoint where IEEEComparable == Self {
    var ieeeComparable: Self { return self }
}

struct Float: FloatingPoint {
   /* IEEE comparison */   
   static func compare(_: Float, to: Float) -> ComparisonResult { ... }

   /* Quirky Float where .Nan == .Nan, +0.0 == -0.0 etc... */
   struct StandardComparable: FloatingPoint {
     static func compare(_: StandardComparable, to: StandardComparable) -> ComparisonResult { ... }
   }
   var comparable: StandardComparable { return StandardComparable(self) }
}

The idea is that the invisible context-sensitive comparison quirks would become visible:

if values.contains(.NaN) { // uses IEEE rules, so is always false
    print(values.filter { $0 != .NaN })
}

if values.contains(where: { $0.comparable == .NaN }) { // opt-in to stdlib quirks
    print(values.filter { $0.comparable != .NaN }) // no NaNs
}

- Karl

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


More information about the swift-evolution mailing list