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
···
On 16 Apr 2017, at 05:32, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:
on Sat Apr 15 2017, Xiaodi Wu <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
On Sat, Apr 15, 2017 at 3:12 PM, Dave Abrahams via swift-evolution < >> swift-evolution@swift.org> wrote:
on Thu Apr 13 2017, Xiaodi Wu <swift-evolution@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 confusionand
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 describeTo 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.