[swift-dev] Rationalizing FloatingPoint conformance to Equatable

Xiaodi Wu xiaodi.wu at gmail.com
Thu Oct 19 18:28:42 CDT 2017

Ben Cohen asked to continue this conversation on swift-dev--

Included in PR #12503 is a small tweak to accommodate so-called
"exceptional values" (such as NaN) in comparisons of array equality.

Currently, `Array.==` first looks to referential equality of underlying
buffers, then (if false) compares elementwise equivalence as defined by
`Element.==`. As a consequence, an array of NaN compares equal to another
array of NaN if they share the same buffer, but otherwise false. This is an
undesirable outcome.

In my analysis, this problem in fact comprises two issues.

# The proximate issue

The proximate issue is that `Array.==` is exposing an implementation detail
that the user should not need to reason about. Regardless of whether or not
it is wise for the partial equivalence relation `FloatingPoint.==` to be
deemed as semantically satisfactory for the purposes of `Equatable`, the
fact remains that Swift deliberately ships with such a conformance, and
`Array.==` should not blow up given that explicitly contemplated design
decision. This PR resolves that proximal issue.

I believe that this fix is strictly an improvement upon the status quo and
orthogonal to the second, more distal issue.

# The distal issue

The distal issue has to do with `FloatingPoint.==` and the conformance of
that protocol to `Equatable`.

The last time that this topic was brought up, competing demands voiced by
different people were not possible to reconcile:

1) Equatable `==` must be a full equivalence relation.

2) Floating-point types must conform to `Equatable` and not to some
alternative such as `PartiallyEquatable`.

3) Floating-point `==` must be IEEE-compliant (at least in the concrete
context), and therefore *not* a full equivalence relation.

4) IEEE-compliant floating-point equivalence must be spelled `==` and not
some alternative such as `&==`.

5) Behavior of `==` must not change between generic and concrete contexts.

Any solution must abolish one or more of the above must-haves. The
questions to be settled before any API-changing issues are broached are

A) Must `Equatable.==` be a full equivalence relation?

By explicitly allowing exceptions in the documentation written for
`Comparable`, and by vending `FloatingPoint : Equatable`, the Swift
standard library currently tolerates `==` being a partial equivalence
relation, but some standard library algorithms assume that it is a full
equivalence relation.

In my view, this is an open topic for debate, as a non-trivial number of
generic algorithms can tolerate a partial equivalence relation (if NaN !=
NaN, then it's perfectly correct for Set not to deduplicate NaNs). Other
algorithms can be explicitly documented to require all elements to be in
the domain of arguments for a full equivalence relation and/or take a
custom predicate.

B) Must floating-point types conform to `Equatable`?

In my view, this is clear, and I don't believe it's a very controversial
opinion. Chris Lattner (I think?) was the one who pointed out that the Rust
experience with PartialEq and Eq, PartialOrd and Ord has been unsuccessful
because the same problems are being punted to PartialEq and PartialOrd. The
idea is that people avoid writing generic algorithms that exclude the use
of floating-point types; therefore, whatever protocol guarantees some
relation named `==` and includes both floating-point types and other types
will be the one that's used for generic algorithms, even if users need full
equivalence semantics not guaranteed by that protocol.

C) Must (concrete) floating-point `==` be IEEE-compliant?

In my view, yes; the alternative would make Swift utterly unsuitable for
numerics. I don't believe this is very controversial.

D) Must floating-point IEEE-compliant equivalence be spelled `==`?

In my view, this is something open for debate. I see no reason why it
cannot be migrated to `&==` if it were felt that `==` *must* be a full
equivalence relation. I believe this is controversial, however.

E) Must the behavior of `==` be the same in generic and concrete contexts?

In my view, it absolutely must be. Differing behavior in generic and
concrete contexts is simply too subtle to be understandable to the reader.
The explanation that a method on `Float` is a "floating-point context" but
a method on `[Float]` is *not a "floating point context"* is, IMO,
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-dev/attachments/20171019/35d4e359/attachment.html>

More information about the swift-dev mailing list