[swift-evolution] [pitch] Comparison Reform

Xiaodi Wu xiaodi.wu at gmail.com
Sat Apr 15 17:26:59 CDT 2017


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. The
special casing of certain values already makes comparison operations
difficult to understand; I guess I'm simply stating what is unavoidably
true, that having a "non-special-cased" comparison *in addition* to that
adds additional difficulty.

One example where this comes to mind is this: `XCTAssertEqual(+0.0, -0.0)`
will pass or fail depending on whether XCTAssertEqual provides a
FP-specific specialization. This is something that cannot be determined by
reading the testing code itself: that is, it requires not merely knowledge
about whether or not the call site "knows" that it's operating on a FP
type, but also knowledge about whether XCTest itself is FP-aware.

The issue increases in difficulty when it comes to non-stdlib types.
Suppose I were to write a Rational type. (I am writing a Rational type, but
we can talk about that later.) This Rational type is capable of
representing NaN (`(0 / 0 as Rational<Int>).isNaN == true`) and, for
consistency with FP types, `Rational<Int>.nan != Rational<Int>.nan`. If
this proposal is implemented, *I* would be responsible for making XCTest
Rational-aware, vending `XCTAssertEqual<Rational<T>>(_:_:)`. In fact, to
ensure that users of Rational get the expected behavior everywhere, I would
have to vend special Rational-aware versions of every stdlib, core library,
and _third-party_ function that is FP-aware, which it is not possible to do.

> Actually, the fact that this behavior cannot even be achieved without
> > currently non-existent compiler features means that it is not possible
> > to understand what's truly going on without reading *this document*,
>
> This doesn't seem like a reasonable argument.  New compiler features get
> documented outside of the proposals they come from.  Nobody's going to
> have to go read a proposal to understand what @implements means.


I do not mean that I seriously believe @_implements will never be
documented. (Although, as an underscored attribute, it does not need to be
documented outside of the apple/swift repo, in the same way that
@_transparent and its ilk are not documented outside the repo.) I am simply
saying that fully understanding how `Comparable` and `FloatingPoint`
interact with each other will require learning one additional advanced
feature of the language than is required now, and that this feature does
not even currently exist.


> > after mastering *both* IEEE floating point *and* Swift
> > generics/protocols/extensions/static vs. dynamic dispatch. All to use
> > `==` correctly.
>
> I don't understand this argument.  The *whole* point of this proposal is
> that you use `==` correctly *without* the need for any special knowledge.
>
> > Which is to say, most people will simply not even know if they happen
> > to be using the `==` they did not intend to use.
>
> Most people aren't aware that IEEE comparison is quirky and don't know
> what they intend with respect to those semantics, but anyone who *is*
> aware of his intention has an easy way to understand what's happening.
> Does this code know it's operating on floating point numbers?  If so,
> it's IEEE.  If not, it's an equivalence relation.
>
> > I think consideration should be given to a design that achieves a
> > user-facing but not onerous differentiation between level 1 and level 2
> > equality. However, the only one I can think of is essentially a different
> > shade of the `PartiallyComparable` alternative already outlined in the
> > document.
>
> I am *deeply* opposed to such a protocol.  It is purely syntactic in
> nature and thus useless for correctly constraining generic algorithms.
>

But there do exist types for which some pairs of values cannot be compared
to each other. A generic algorithm that blows up if a value cannot be
compared to another value should not operate on such types, no?


> People will use it anyway, resulting in algorithms that statically
> accept, but are incorrect for, floating point.  In my opinion there's
> only one feasible answer that doesn't use the static/dynamic distinction
> we've proposed: throw IEEE semantics under the bus, making it available
> only under a different syntax.
>
> This would be a drastic move whose primary downside is that floating
> point code ported from C would need to be carefully audited and may
> become less easy to read.  But at least it would be viable.
>

Yes I agree that it would be a much more drastic move that would make Swift
difficult to use for numerics, and I, at least, would not want to go down
that road.


> > Yet I cannot help but think that the rejected alternative may be
> > advantageous in one key aspect. `FloatingPoint` comparison is in a
> > sense "less refined" (not exactly precise language, I know) than the
> > level 2 ordering proposed here, at least in the sense that the latter
> > offers more semantic guarantees about the relationships between
> > comparison operators.
>
> No, they are effectively refinement siblings.  If it was a hierarchical
> relationship, we could just make the floating point types conform to
> Equatable.  But floating point types are *required* by IEEE to have
> comparison semantics that conflict with Equatable.
>
> > It's weird that the less refined `FloatingPoint` refines the more
> > refined `Comparable`,
>
> Do you want to be able to sort floating point numbers without providing
> a comparison predicate (one that has to be spelled less obviously than
> "<")?  If so, floating point numbers must be Comparable.  If not, we
> could talk about breaking this refinement relationship.
>
> > and I think the acrobatics with compiler support illustrate how the
> > design is actively working against Swift's overarching direction.
>
> It's not more acrobatic than things we already do in the standard
> library to ensure that people transparently see the right behavior (see
> .lazy), and we could probably even find ways to do it without language
> features.  It would be less principled, harder to understand, and more
> fragile than designing a language feature that addresses the need
> directly.


I have an incipient idea. It begins with:

enum ComparisonResult {
  case orderedAscending, orderedSame, orderedDescending, unordered
}

I am sure there is something I am missing which makes this design a
horrible idea, but I'm interested in hearing them.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170415/05bad0e4/attachment.html>


More information about the swift-evolution mailing list