Comparable and FloatingPoint types

After reading this thread, on the top of my head the possible solutions are:

  1. Make the generic context change the semantics (this proposal)
  2. Make Equatable and Comparable use a new operator (<=>)
  3. Move IEEE semantics to new operators (&== and &<)
  4. Move IEEE semantics to separate types and implement total ordering in Float/Double
  5. Move IEEE semantics to separate types and trap on .nan (mirroring integer division by zero)
  6. Make Float and Double not conform Equatable/Comparable and possibly create new types that do.
  7. Do nothing and tolerate the flaky results when .nan is involved.

Clearly, something needs to be broken. We can:

  • keep the broken ordering behavior around .nan, or
  • silently break the math code out there comparing .nans with other values, or
  • trap in math code when producing .nan (has a performance cost), or
  • break everyone's code with a huge fanfare by removing conformances used by generic algorithms, or
  • have the compiler pick semantics depending of the generic context.

One advantage of keeping the current behavior is that it is consistent with other languages, so it is already well known and thus can be reasonably expected. Even though I think everyone agrees the current behavior is not great, at least it is easy to explain.


Perhaps we could instead embrace the current behavior and teach common algorithms to diagnose problems? For instance contains(_ member: T) could have a precondition that member == member, trapping on .nan with a useful error message. And sort() could perform the an equality check with == whenever two values appear equal based on <. Those are general sanity checks that can be useful with any type.

1 Like

I apologize for the snark. In rereading things, I may have been reading aggression in Xiaodi's response that wasn't there.

In this case, I am confused. Doesn't the initial proposal silently change the behavior of < and == based on context?

In my mind, creating &== and &< is less source breaking (since we could also have a migration path which changes all == to &==, etc... when dealing with FloatingPoint). I don't see any reason why this couldn't be easily done in the Swift 5 timeframe. I think it should be at least considered as a serious option.

7 Likes

Imho the current direction of the discussion is a general problem in SE:
There is a lack of long-term planning which should be addressed.
We see the same issue in the review for SE-0235 (because it is still undecided if we want to have typed throws), and SE-0229 (because there is no clear statement what generics should support in the future).

Even those of us who prefer a radical overhaul most likely won't insist in having this done for Swift 5 - but it would be rather unfortunate if it is decided to implement a small fix now which doesn't play well with the final, clean solution (if there is any).

So, let's just imagine we agree (I know, that's wack ;-) on a concept for number types that protects newbies from common errors without compromising performance or breaking expectations of experts.
In this scenario, would it really make sense to break compatibility now instead of waiting a year for that perfect solution? I don't think so.

Even if there are to many people in this forum to have a productive discussion about how that "perfect solution" would look like, that still could happen in the panel that actually decides about all those small additive changes that go through SE.

Maybe the forum should break out "perfect solution" pitches into separate discussions that can be void of "in the given timeframe" or "non-starter because of source-breakage" arguments because anything goes.

Then evolution pitches could be more focused on current restrictions of the problem space. (Writing this though I already realize that "current restrictions of the problem space" is again very much debatable)

I'll defer to others on most of the meta-discussion, but to me the proposal as written makes sense and is an improvement over the status quo, provided some more generalised syntax eventually arrives. As I see it (and this is mainly restating the logic of the proposal):

  • Floating point types do not have a valid conformance to Comparable or Equatable.
  • We want floating point types to semantically conform to Comparable and Equatable.
  • We should therefore provide a proper implementation of Comparable and Equatable's requirements for floating point types.
  • Such an implementation will overlap with the existing IEEE operators (<, ==). As is the case in Swift for protocol dispatch in general (e.g. default implementations on a protocol), the most specific (IEEE) operators will always be picked in a non-generic context, with the Comparable/Equatable operators in generic contexts.
  • It would be useful to force the use of the generic operator in non-generic contexts. Therefore, at some point in the future, syntax should be added to specify e.g. Equatable.== instead of the (Float, Float) operator.

This same approach would be useful in other cases, too; for example, if two different protocols which a type wishes to conform to both have a same-signature requirement.

3 Likes

I think I may have thought of a migration strategy that is 90% source compatible, and ends up with FP.== being Equatable correct, and &== being IEEE correct:

In Swift N (preferably 5):

  • add &== etc. to FP as synonyms for current (IEEE) compare
    These would be added to the FloatingPoint protocol
  • make == etc. use Equatable correct semantics in generic code
  • add a private func FP_eqatable_thunk etc. in each project file with FP.== etc.
    Note: added to each project file as private to prevent it from being in any APIs / ABIs
  • depreciate FP.== etc. with fixits to both &== and FP_eqatable_thunk etc.

In swift N+1 (preferably 6)

  • un-deprecate FP.== etc and switch them to Equatable correct semantics
  • add an optional migrator fixit to remove FP_eqatable_thunk etc.

The only source-incompatible step I see is making == behave differently in generic code, but most solutions (except "do nothing") have that incompatibility.