[pitch] Comparison Reform

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

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.

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.
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.

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.

···

on Thu Apr 13 2017, Xiaodi Wu <swift-evolution@swift.org> wrote:

Anyway, so much for that about the design overall.

As for nitpicking details, I agree with others and think it's a poor
precedent to say that we're going to use an Obj-C name because it's not
clearly worse than the obvious Swift API guideline-compliant name. When
that's the case, it seems to me that the whole point of having Swift API
naming guidelines and making that huge migration from Swift 2 to 3 was so
that going forward we could name things consistently using one consensus
style. `compare(_:)` does not merit a term-of-art exception when the Swift
name is clearly `compared(to:)`.

On Thu, Apr 13, 2017 at 3:17 PM, Ben Cohen via swift-evolution < > swift-evolution@swift.org> wrote:

Hi swift evolution,

Here’s another pitch, for The Propoosal Formerly Known As Spaceship.
Comparison Reform

   - Proposal: SE-NNNN
   - Authors: Robert Widmann <https://github.com/codafi&gt;, Jaden Geller
   <https://github.com/jadengeller&gt;, Harlan Haskins
   <https://github.com/harlanhaskins&gt;, Alexis Beingessner
   <https://github.com/Gankro&gt;, Ben Cohen
   <https://github.com/airspeedswift&gt;
   - Status: *Awaiting review*
   - Review manager: TBD

Introduction

This proposal is for changes that we believe should be made to the
existing comparison system by:

   - Making FloatingPoint comparison context sensitive, so that its
   Comparable conformance provides a proper total ordering.
   - Introducing a new ternary-valued compare(_ other: Self) ->
   ComparisonResult method.
   - Removing unnecessary customization points from Comparable.

Motivation

The motivation comes from several independent points:

1: The standard comparison operators have an intuitive meaning to
programmers. Swift encourages encoding that in an implementation of
Comparable that respects the rules of a total order
<https://en.wikipedia.org/wiki/Total_order&gt;\. The standard library takes
advantage of these rules to provide consistent implementations for sorting
and searching generic collections of Comparable types.

Not all types behave so well in this framework, unfortunately. There are
cases where the semantics of a total order cannot apply while still
maintaining the traditional definition of “comparison” for these types.
Take, for example, sorting an array of Floats. Today, Float’s instance of
Comparable follows IEEE-754 and returns false for all comparisons of NaN.
In order to sort this array, NaN s are considered outside the domain of <,
and the order of a sorted array containing them is unspecified. Similarly,
a Dictionary keyed off floats can leak entries and memory.

2: Generic algorithms in the Swift Standard Library that make use of the
current Comparable protocol may have to make extra comparisons to
determine the ordering of values when <, ==, and > should have different
behaviours. Having a central operation to return complete ordering
information should provide a speedup for these operations.

3: The existing comparison operators don’t “generalize” well. There’s no
clean way to add a third or fourth argument to < to ask for non-default
semantics. An example where this would be desirable would be specifying the
locale or case-sensitivity when comparing Strings.

4: Comparable is over-engineered in the customization points it provides:
to our knowledge, there’s no good reason to ever override >=, >, or <=.
Each customization point bloats vtables and mandates additional dynamic
dispatch.

5: When quickly writing a Comparable type, it is easier to implement a
single ternary statement than to separately implement == and <.
Proposed solutionComparisonResult

Foundation’s ComparisonResult type will be mapped into Swift as

@objc public enum ComparisonResult : Int {
  case orderedAscending = -1
  case orderedSame = 0
  case orderedDescending = 1
}

Comparable

Comparable will be changed to have a new ternary comparison method: compare(_
other: Self) -> ComparisonResult. x.compare(y) specifies where to place x
relative to y. So if it yields .orderedAscending, then x comes before y.
This will be considered the new “main” dispatch point of Comparable that
implementors should provide.

Most code will continue to use < or ==, as it will be optimal for their
purposes. However code that needs to make a three-way branch on comparison
can use the potentially more efficient compare. Note that compare is only
expected to be more efficient in this specific case. If a two-way branch is
all that’s being done, < will be more efficient in many cases (if only
because it’s easier for the optimizer).

For backwards compatibility reasons, compare will have a default
implementation defined in terms of <, but to enable only using compare, <
and == will also have default implementations in terms of compare.

The compiler will verify that either compare, or < and ==, are provided
by every type that claims to conform to Comparable. This will be done in
some unspecified way unavailable outside the standard library (it can be
made available to in the future, but that’s an unnecessary distraction for
this proposal).

Types that wish to provide comparison “variants” can do so naturally by
adding compare methods with additional arguments. e.g. String.compare(_
other: Self, in: Locale) -> ComparisonResult. These have no
language-level connection to Comparable, but are still syntactically
connected, implying the same total order semantics. This makes them easier
to discover, learn, and migrate to.

To reduce bloat, the operators <=, >=, and > will be removed from the set
of requirements that the Comparable protocol declares. These operators
will however continue to exist with the current default implementations.
FloatingPoint

No changes will be made to the FloatingPoint protocol itself. Instead,
new extensions will be added to it to change the behaviour of comparison.

The new behaviour centers around the fact that compare(_: Self) ->
ComparisonResult will provide a total ordering that’s consistent with
Level 2 in the IEEE 754 (2008) spec. This is mostly the same as the
standard (Level 1) IEEE ordering, except:

   - -0 < +0
   - NaN == NaN
   - NaN > +Inf (an arbitrary choice, NaN can be placed anywhere in the
   number line)

Level 2’s distinguishing of -0 and +0 is a bit strange, but is consistent
with Equatable’s Substitutability requirement. -0 and +0 have different
behaviours: 1/-0 = -Inf while 1/+0 = +Inf. The main problem this can lead
to is that a keyed collection may have two “0” entries. In practice this
probably won’t be a problem because it’s fairly difficult for the same
algorithm to produce both -0 and +0. Any algorithm that does is also
probably concerned with the fact that 1.0E-128 and 2.0E-128 are
considered distinct values.

Note: IEEE specifies several other potential total orderings: level 3,
level 4, and the totalOrder predicate. For our purposes, these orderings
are too aggressive in distinguishing values that are semantically
equivalent in Swift. For most cases, the relevant issue is that they
distinguish different encodings of NaN. For more exotic encodings that
don’t guarantee normalization, these predicates also consider 10.0e0 <
1.0e1 to be true. An example where this can occur is *IEEE-754 decimal
coded floating point*, which FloatingPoint is intended to support.

We will then make the comparison operators (<, <=, ==, !=, >=, >)
dispatch to one of compare(_:) or FloatingPoint’s IEEE comparison methods
(isLess, isEqual, isLessThanOrEqualTo) based on the context.

   - If the context knows the type is FloatingPoint, then level 1
   ordering will be used.
   - If the context only knows the type is Comparable or Equatable, then
   level 2 ordering will be used.

This results in code that is explicitly designed to work with
FloatingPoint types getting the expected IEEE behaviour, while code that is
only designed to work with Comparable types (e.g. sort and Dictionary)
gets more reasonable total ordering behaviour.

To clarify: Dictionary and sort won’t somehow detect that they’re being
used with FloatingPoint types and use level 1 comparisons. Instead they
will unconditional use level 2 behaviour. For example:

let nan = 0.0/0.0
func printEqual<T: Equatable>(_ x: T, _ y: T) {
  print(x == y)
}
func printEqualFloats<T: FloatingPoint>(_ x: T, _ y: T) {
  print(x == y)
}
print(nan == nan) // false, (concrete)
printEqual(nan, nan) // true, (generic Equatable but not FloatingPoint)
printEqualFloats(nan, nan) // false, (generic FloatingPoint)

If one wishes to have a method that works with all Equatable/Comparable
types, but uses level 1 semantics for FloatingPoint types, then they can
simply provide two identical implementations that differ only in the bounds:

let nan = 0.0/0.0
func printEqual<T: Equatable>(_ x: T, _ y: T) {
  print(x == y)
}
func printEqual<T: FloatingPoint>(_ x: T, _ y: T) {
  print(x == y)
}

printEqual(0, 0) // true (integers use `<T: Equatable>` overload)
printEqual(nan, nan) // false (floats use `<T: FloatingPoint>` overload)

As a result of this change, hashing of floats must be updated to make all
NaNs hash equally. -0 and +0 will also no longer be expected to hash
equally. (Although they might as an implementation detail – equal values
must hash the same, unequal values *may* hash the same.)
Misc Standard Library

Types that conform to Comparable should be audited for places where
implementing or using Comparable would be a win. This update can be done
incrementally, as the only potential impact should be performance. As an
example, a default implementation of compare(_:) for Array will likely be
suboptimal, performing two linear scans to determine the result in the
worst-case. (See the default implementation provided in the detailed
design.)

Some free functions will have <T: FloatingPoint> overloads to better
align with IEEE-754 semantics. This will be addressed in a follow-up
proposal. (example: min and max)
Detailed Design

The protocols will be changed as follows:

ComparisonResult, currently a type found in Foundation, will be sunk into
the Swift Standard Library:

@objc public enum ComparisonResult: Int, Equatable {
  case orderedAscending = -1
  case orderedSame = 0
  case orderedDescending = 1
}
public protocol Comparable: Equatable {
  func compare(_ other: Self) -> ComparisonResult

  static func < (lhs: Self, rhs: Self) -> Bool
}
extension Comparable {
  func compare(_ other: Self) -> ComparisonResult {
    if self == other {
      return .orderedSame
    } else if self < other {
      return .orderedAscending
    } else {
      return .orderedDescending
    }
  }
}
public func < <T: Comparable>(lhs: T, rhs: T) -> Bool {
  return lhs.compare(rhs) == .orderedAscending
}
// IEEE comparison operators (these implementations already exist in std)extension FloatingPoint {
  public static func == (lhs: T, rhs: T) -> Bool {
    return lhs.isEqual(to: rhs)
  }

  public static func < (lhs: T, rhs: T) -> Bool {
    return lhs.isLess(than: rhs)
  }

  public static func <= (lhs: T, rhs: T) -> Bool {
    return lhs.isLessThanOrEqualTo(rhs)
  }

  public static func > (lhs: T, rhs: T) -> Bool {
    return rhs.isLess(than: lhs)
  }

  public static func >= (lhs: T, rhs: T) -> Bool {
    return rhs.isLessThanOrEqualTo(lhs)
  }
}

// Comparable comparison operators (provides a total ordering)extension FloatingPoint {
  @_inline
  public func compare(_ other: Self) -> ComparisonResult {
    // Can potentially be implemented more efficiently -- this is just the clearest version
    if self.isLess(than: other) {
      return .orderedAscending
    } else if other.isLess(than: self) {
      return .orderedDescending
    } else {
      // Special cases

      // -0 < +0
      if self.isZero && other.isZero {
        // .plus == 0 and .minus == 1, so flip ordering to get - < +
        return (other.sign as Int).compare(self.sign as Int)
      }

      // NaN == NaN, NaN > +Inf
      if self.isNaN {
        if other.isNaN {
          return .orderedSame
        } else {
          return .orderedDescending
        }
      } else if other.isNaN {
        return .orderedAscending
      }

      // Otherwise equality agrees with normal IEEE
      return .orderedSame
    }
  }

  @_implements(Equatable.==)
  public static func _comparableEqual(lhs: Self, rhs: Self) -> Bool {
    lhs.compare(rhs) == .orderedSame
  }

  @_implements(Comparable.<)
  public static func _comparableLessThan(lhs: Self, rhs: Self) -> Bool {
    lhs.compare(rhs) == .orderedDescending
  }
}

Note that this design mandates changes to the compiler:

   - @_implements (or an equivalent mechanism) must be implemented to get
   the context-sensitive FloatingPoint behaviour.
   - The compiler must verify that either == and <, or compare(_:) is
   overridden by every type that conforms to Comparable.

Source compatibility

Users of ComparisonResult will be able to use it as normal once it
becomes a standard library type.

Existing implementors of Comparable will be unaffected, though they
should consider implementing the new compare method as the default
implementation may be suboptimal.

Consumers of Comparable will be unaffected, though they should consider
calling the compare method if it offers a performance advantage for their
particular algorithm.

Existing implementors of FloatingPoint should be unaffected – they will
automatically get the new behaviour as long as they aren’t manually
implementing the requirements of Equatable/Comparable.

Existing code that works with floats may break if it’s relying on some
code bounded on <T: Equatable/Comparable>providing IEEE semantics. For
most algorithms, NaNs would essentially lead to unspecified behaviour, so
the primary concern is whether -0.0 == +0.0 matters.
ABI stability

This must be implemented before ABI stability is declared.
Effect on API resilience

N/A
Alternatives ConsideredSpaceship

Early versions of this proposal aimed to instead provide a <=> operator
in place of compare. The only reason we moved away from this was that it
didn’t solve the problem that comparison didn’t generalize.

Spaceship as an operator has a two concrete benefits over compare today:

   - It can be passed to a higher-order function
   - Tuples can implement it

In our opinion, these aren’t serious problems, especially in the long term.

Passing <=> as a higher order function basically allows types that aren’t
Comparable, but do provide <=>, to be very ergonomically handled by
algorithms which take an optional ordering function. Types which provide
the comparable operators but don’t conform to Comparable are only pervasive
due to the absence of conditional conformance. We shouldn’t be designing
our APIs around the assumption that conditional conformance doesn’t exist.

When conditional conformance is implemented, the only
should-be-comparable-but-aren’t types that will remain are tuples, which
we should potentially have the compiler synthesize conformances for.

Similarly, it should one day be possible to extend tuples, although this
is a more “far future” matter. Until then, the (T, T) -> Bool predicate
will always also be available, and < can be used there with the only
downside being a potential performance hit.
Just Leave Floats Alone

The fact that sorting floats leads to a mess, and storing floats can lead
to memory leaks and data loss isn’t acceptable.
Just Make Floats Only Have A Total Order

This was deemed too surprising for anyone familiar with floats from any
other language. It would also probably break a lot more code than this
change will.
Just Make Floats Not Comparable

Although floats are more subtle than integers, having places where
integers work but floats don’t is a poor state of affairs. One should be
able to sort an array of floats and use floats as keys in data structures,
even if the latter is difficult to do correctly.
PartialComparable

PartialComparable would essentially just be Comparable without any stated
ordering requirements, that Comparable extends to provide ordering
requirements. This would be a protocol that standard IEEE comparison could
satisfy, but in the absence of total ordering requirements,
PartialComparable is effectively useless. Either everyone would consume
PartialComparable (to accept floats) or Comparable (to have reasonable
behaviour).

The Rust community adopted this strategy to little benefit. The Rust libs
team has frequently considered removing the distinction, but hasn’t because
doing it backwards compatibly would be complicated. Also because merging
the two would just lead to the problems Swift has today.
Different Names For compare and ComparisonResult

A few different variants for ComparisonResult and its variants were
considered:

   - Dropping the ordered part of ComparisonResult’s cases e.g. .ascending
   - Naming of ComparisonResult as SortOrder
   - enum Ordering { case less, equal, greater } (as used by Rust
   <https://doc.rust-lang.org/std/cmp/enum.Ordering.html&gt;\)
   - Case values of inOrder, same, outOfOrder

The choice of case names is non-trivial because the enum shows up in
different contexts where different names makes more sense. Effectively, one
needs to keep in mind that the “default” sort order is ascending to map
between the concept of “before” and “less”.

The before/after naming to provide the most intuitive model for custom
sorts – referring to ascending or less is confusing when trying to
implement a descending ordering. Similarly the inOrder/outOfOrder naming
was too indirect – it’s more natural to just say where to put the element.
If the enum should focus on the sorting case, calling it SortOrder would
help to emphasize this fact.

This proposal elects to leave the existing Foundation name in-place. The
primary motivation for this is that use of the compare function will be
relatively rare. It is expected that in most cases users will continue to
make use of == or <, returning boolean values (the main exception to this
will be in use of the parameterized String comparisons). As such, the
source compatibility consequences of introducing naming changes to an
existing type seems of insufficient benefit.

The method compare(_:) does not fully comport with the API naming
guidelines. However, it is firmly established with current usage in
Objective-C APIs, will be fairly rarely seen/used (users will usually
prefer <, == etc), and alternatives considered, for example compared(to:),
were not a significant improvement.
Add Overloads for (T, T) -> ComparisonResult

It would be slightly more ergonomic to work with ComparisonResult if
existing methods that took an ordering predicate also had an overload for (T,
T) -> ComparisonResult. As it stands, a case-insensitive sort must be
written as follows:

myStrings.sort { $0.compare(_ other: $1, case: .insensitive) == .orderedAscending }

With the overload, one could write:

myStrings.sort { $0.compare($1, case: .insensitive) }

we decided against providing these overloads because:

   - The existing algorithms in the standard library can’t benefit from
   them (only binary comparisons).
   - They bloat up the standard library (and any library which intends to
   match our API guidelines).
   - They potentially introduce confusion over “which” comparison
   overload to use.

And because we can change our mind later without concern for source or ABI
stability, as these overloads would be additive.
Future Work

This section covers some topics which were briefly considered, but were
identified as reasonable and possible to defer to future releases.
Specifically they should be backwards compatible to introduce even after
ABI stability. Two paths that are worth exploring:
Ergonomic Generalized Comparison for Keyed Containers

Can we make it ergonomic to use an (arbitrary) alternative comparison
strategy for a Dictionary or a BinaryTree? Should they be type-level
Comparators, or should those types always store a (Key, Key) ->
ComparisonResult closure?

We can avoid answering this question because Dictionary is expected to
keep a relatively opaque (resilient) ABI for the foreseeable future, as
many interesting optimizations will change its internal layout. Although if
the answer is type-level, then Default Generic Parameters must be accepted
to proceed down this path.
ComparisonResult Conveniences

There are a few conveniences we could consider providing to make
ComparisonResult more ergonomic to manipulate. Such as:

// A way to combine orderingsfunc ComparisonResult.breakingTiesWith(_ order: () -> ComparisonResult) -> ComparisonResult

array.sort {
  $0.x.compare($0.y)
  .breakingTiesWith { $0.y.compare($1.y) }
  == .orderedAscending
}

and

var inverted: ComparisonResult
// A perhaps more "clear" way to express reversing order than `y.compared(to: x)`
x.compare(y).inverted

But these can all be added later once everyone has had a chance to use
them.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
-Dave

Oh, I definitely overlooked that part. Thanks for that clarification! I had assumed the compiler changes were only necessary for allowing circular default implementations while still requiring at least one requirement be implemented.

That said, it’s possible to implement something very, very similar without compiler changes:

Cheers,
Jaden Geller

···

On Apr 13, 2017, at 5:58 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Jaden, the proposal literally says that a compiler feature named "@_implements" is necessary for the proposed design to work. You can see WIP for that feature in the apple/swift repo.

On Thu, Apr 13, 2017 at 19:55 Jaden Geller <jaden.geller@gmail.com <mailto:jaden.geller@gmail.com>> wrote:

On Apr 13, 2017, at 5:18 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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*, after mastering *both* IEEE floating point *and* Swift generics/protocols/extensions/static vs. dynamic dispatch. All to use `==` correctly. Which is to say, most people will simply not even know if they happen to be using the `==` they did not intend to use.

If I understand correctly, I think you’re mistaken. The compiler already selects overloads based on generic context. If `T: FloatingPoint`, then it’ll choose the `==` with signature `<T: FloatingPoint> (T, T) -> Bool`. If `T: Equatable`, then it’ll choose the `==` with signature `<T: Equatable> (T, T) -> Bool`. No new compiler features are necessary for this specific behavior.

Cheers,
Jaden Geller

This is a sugar request, but if we are rearranging these things anyway,
can we *please* add the ‘≤’, ‘≥’, and ‘≠’ operators to comparable. I know
they would do the same thing as ‘<=‘, ‘>=‘, and ‘!=‘, but they can’t really
be used for anything else without being confusing (because they literally
have that meaning in mathematics), and they make my code so much more
readable.

This is vastly increasing API surface area for every user of Swift for
literally no functionality. Why isn't it sufficient that Swift allows you
to do this for yourself without restriction?

···

On Thu, Apr 13, 2017 at 7:02 PM, Jonathan Hull via swift-evolution < swift-evolution@swift.org> wrote:

Thanks,
Jon

On Apr 13, 2017, at 1:24 PM, Ben Cohen via swift-evolution < > swift-evolution@swift.org> wrote:

Online copy here:

https://github.com/airspeedswift/swift-evolution/blob/
fa007138a54895e94d22e053122ca24ffa0b2eeb/proposals/NNNN-
ComparisonReform.md

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Right; clearly, the difference between the proposed @_implements design and
yours will be subtle (but, when it strikes, a totally different function is
invoked) and it will be difficult for even advanced Swift users to
understand.

···

On Thu, Apr 13, 2017 at 20:29 Jaden Geller <jaden.geller@gmail.com> wrote:

Oh, I definitely overlooked that part. Thanks for that clarification! I
had assumed the compiler changes were only necessary for allowing circular
default implementations while still requiring at least one requirement be
implemented.

That said, it’s possible to implement something very, very similar without
compiler changes:

Static Context · GitHub

Cheers,
Jaden Geller

On Apr 13, 2017, at 5:58 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Jaden, the proposal literally says that a compiler feature named
"@_implements" is necessary for the proposed design to work. You can see
WIP for that feature in the apple/swift repo.

On Thu, Apr 13, 2017 at 19:55 Jaden Geller <jaden.geller@gmail.com> wrote:

On Apr 13, 2017, at 5:18 PM, Xiaodi Wu via swift-evolution < >> swift-evolution@swift.org> wrote:

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*, after
mastering *both* IEEE floating point *and* Swift
generics/protocols/extensions/static vs. dynamic dispatch. All to use `==`
correctly. Which is to say, most people will simply not even know if they
happen to be using the `==` they did not intend to use.

If I understand correctly, I think you’re mistaken. The compiler already
selects overloads based on generic context. If `T: FloatingPoint`, then
it’ll choose the `==` with signature `<T: FloatingPoint> (T, T) -> Bool`.
If `T: Equatable`, then it’ll choose the `==` with signature `<T:
> (T, T) -> Bool`. No new compiler features are necessary for this
specific behavior.

Cheers,
Jaden Geller

> 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.

···

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:

And actually, it’s a shame that kind of just snuck in as a footnote, because I think it is needed in the language anyway.

I’ve thought about more direct approaches to tackling this problem, such as “conformance regions” or named conformances (compiler-generated wrapper structs), which could allow you to conform to the same protocol more than once with special resolution features (e.g. think String.CharacterView being the default, named “Collection” conformance for String), or to manually specify which members get resolved as witnesses.

But in this proposal it’s just an implementation detail. We still don’t have a general solution to conflicting names for the members of different protocols.

- Karl

···

On 14 Apr 2017, at 02:58, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

Jaden, the proposal literally says that a compiler feature named "@_implements" is necessary for the proposed design to work. You can see WIP for that feature in the apple/swift repo.

On Thu, Apr 13, 2017 at 19:55 Jaden Geller <jaden.geller@gmail.com <mailto:jaden.geller@gmail.com>> wrote:

On Apr 13, 2017, at 5:18 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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*, after mastering *both* IEEE floating point *and* Swift generics/protocols/extensions/static vs. dynamic dispatch. All to use `==` correctly. Which is to say, most people will simply not even know if they happen to be using the `==` they did not intend to use.

If I understand correctly, I think you’re mistaken. The compiler already selects overloads based on generic context. If `T: FloatingPoint`, then it’ll choose the `==` with signature `<T: FloatingPoint> (T, T) -> Bool`. If `T: Equatable`, then it’ll choose the `==` with signature `<T: Equatable> (T, T) -> Bool`. No new compiler features are necessary for this specific behavior.

Cheers,
Jaden Geller
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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.
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.

Yeah, I think it should really be considered. An IEEEComparable protocol, defining methods such as “isEqualTo”, “isLessThan”, etc would make the distinction clearer.

And, as you said, not many people really know or care about the IEEE comparison special cases. It’s a bit of an expert field. Is it that bad to ask experts who explicitly care about the order between +/- 0 or different NaNs to use a special function for that behaviour?

I suppose it needs to be weighed up with something like: (number of people ignorant of the difference) x (probability of unexpected result) x (significance of unexpected result, if estimable).

How often do non-floating point experts even use FloatingPoint as a protocol (as opposed to Float/Double)?

- Karl

One benefit of the idea of using comparison metrics instead of changing comparable, is that you could just have two metrics on double. The default one which is closer to what we have now (or the new thing Xiaodi suggested with trapping NaN), and one which matches IEEE. You could in fact make a metric for each level if desired.

Thanks,
Jon

···

On Apr 13, 2017, at 8:30 PM, Jonathan Hull via swift-evolution <swift-evolution@swift.org> wrote:

One more thought. I am generally in favor of this proposal, but since it is in the pitch stage, I wanted to offer an alternative approach that I didn’t see mentioned. Food for thought/discussion.

What if, instead of repurposing comparable (and adding new functions for options like case insensitive compare), we define a comparison metric (with all of the options built in) and then use that to get our comparison result. Comparable things would have a default metric that uses ‘<‘ and ‘==‘ to provide a comparison result.

The metric would have a method which takes two things and returns a ComparisonResult. The two things would usually be the same type, but wouldn’t necessarily have to be.

As a convenience, any type could have a compared(to:, using:) method where you pass a comparison metric to the using parameter and receive a ComparisonResult. Comparable things could add a compared(with:) method and the spaceship operator <=>, which both use the default metric.

Pros:
• Would work without compiler alterations
• You can create metrics that compare items of different types
• Can setup the metric once for algorithms/comparisons with high setup cost
• Things like 'compare(to: other, using: .caseInsensitiveComparison)' fall out of the design without having to create/learn various different versions of compare on different types.
• Spaceship operator <=> for those who want it
• In some cases, it can provide a much more efficient implementation based on underlying structure. For example, you can get a metric from String/Unicode which is optimized for a particular view of that string (say ASCII). Depending on the case, when one of the objects doesn’t match the optimized type, it can either convert or fallback to a more general algorithm… but it should provide a pretty big win when most of the objects have a known structure.

Cons:
• More protocols defined by the design
• Requires an extra struct/class to implement in non-standard cases (e.g. case insensitive compare)
• Probably something else I am missing

Thanks,
Jon

On Apr 13, 2017, at 1:24 PM, Ben Cohen via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Online copy here:

https://github.com/airspeedswift/swift-evolution/blob/fa007138a54895e94d22e053122ca24ffa0b2eeb/proposals/NNNN-ComparisonReform.md

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I think “vastly” is vastly overstating it, especially since they are not customization points… merely aliases. There is nothing else those operators could do without causing confusion. Swift favors clarity, and these operators are much more clear (which I count as a benefit). Also ‘<=‘ looks like an arrow, which I find very distracting in code, as my eye wants to follow it.

I do use this myself in application code, but I can’t add it to my framework code without potentially clashing with others (or myself) who have added the same behavior for themselves. Even though the implementations are exactly the same, it becomes ambiguous which of the (identical) definitions should be used. Having it in the library would mean that everyone would just use that version (and there is only one reasonable implementation, so it wont limit anyone).

I really don’t think there is danger of harm here as you seem to be implying. Anyone who sees ‘≤’ will know what it means, even if they aren’t familiar with Swift. If the implementations point to ‘<=‘, etc… then nothing will get out of sync. There really isn’t any extra maintenance needed.

Thanks,
Jon

···

On Apr 13, 2017, at 5:03 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Thu, Apr 13, 2017 at 7:02 PM, Jonathan Hull via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
This is a sugar request, but if we are rearranging these things anyway, can we *please* add the ‘≤’, ‘≥’, and ‘≠’ operators to comparable. I know they would do the same thing as ‘<=‘, ‘>=‘, and ‘!=‘, but they can’t really be used for anything else without being confusing (because they literally have that meaning in mathematics), and they make my code so much more readable.

This is vastly increasing API surface area for every user of Swift for literally no functionality. Why isn't it sufficient that Swift allows you to do this for yourself without restriction?

Thanks,
Jon

On Apr 13, 2017, at 1:24 PM, Ben Cohen via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Online copy here:

https://github.com/airspeedswift/swift-evolution/blob/fa007138a54895e94d22e053122ca24ffa0b2eeb/proposals/NNNN-ComparisonReform.md

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Agreed. Although to fair, I'm a huge fan of Unicode operators.

- Dave Sweeris

···

On Apr 13, 2017, at 17:51, Jonathan Hull via swift-evolution <swift-evolution@swift.org> wrote:

I think “vastly” is vastly overstating it, especially since they are not customization points… merely aliases. There is nothing else those operators could do without causing confusion. Swift favors clarity, and these operators are much more clear (which I count as a benefit). Also ‘<=‘ looks like an arrow, which I find very distracting in code, as my eye wants to follow it.

I do use this myself in application code, but I can’t add it to my framework code without potentially clashing with others (or myself) who have added the same behavior for themselves. Even though the implementations are exactly the same, it becomes ambiguous which of the (identical) definitions should be used. Having it in the library would mean that everyone would just use that version (and there is only one reasonable implementation, so it wont limit anyone).

I really don’t think there is danger of harm here as you seem to be implying. Anyone who sees ‘≤’ will know what it means, even if they aren’t familiar with Swift. If the implementations point to ‘<=‘, etc… then nothing will get out of sync. There really isn’t any extra maintenance needed.

I think “vastly” is vastly overstating it, especially since they are not
customization points… merely aliases.

It increases the number of methods on Comparable from 5 to 8, and it
increases the number of standard operators (which will already expand if
one-sided ranges are approved), with no functionality gain whatsoever. It
doubles the number of ways to write three operators. And it expands the
standard operators past the ASCII range, which was a choice deliberately
not taken during the debate about how to name SetAlgebra members. If this
is not a vast expansion, I do not know what is.

There is nothing else those operators could do without causing confusion.

Swift favors clarity, and these operators are much more clear (which I
count as a benefit). Also ‘<=‘ looks like an arrow, which I find very
distracting in code, as my eye wants to follow it.

I do use this myself in application code, but I can’t add it to my
framework code without potentially clashing with others (or myself) who
have added the same behavior for themselves. Even though the
implementations are exactly the same, it becomes ambiguous which of the
(identical) definitions should be used. Having it in the library would
mean that everyone would just use that version (and there is only one
reasonable implementation, so it wont limit anyone).

If it were in high demand, a de-facto standard would have arisen by now
vending these symbols as a standalone library. For now, nothing stops you
from defining them for internal use in your library.

I really don’t think there is danger of harm here as you seem to be

···

On Thu, Apr 13, 2017 at 7:51 PM, Jonathan Hull <jhull@gbis.com> wrote:

implying. Anyone who sees ‘≤’ will know what it means, even if they aren’t
familiar with Swift. If the implementations point to ‘<=‘, etc… then
nothing will get out of sync. There really isn’t any extra maintenance
needed.

Thanks,
Jon

On Apr 13, 2017, at 5:03 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Thu, Apr 13, 2017 at 7:02 PM, Jonathan Hull via swift-evolution < > swift-evolution@swift.org> wrote:

This is a sugar request, but if we are rearranging these things anyway,
can we *please* add the ‘≤’, ‘≥’, and ‘≠’ operators to comparable. I know
they would do the same thing as ‘<=‘, ‘>=‘, and ‘!=‘, but they can’t really
be used for anything else without being confusing (because they literally
have that meaning in mathematics), and they make my code so much more
readable.

This is vastly increasing API surface area for every user of Swift for
literally no functionality. Why isn't it sufficient that Swift allows you
to do this for yourself without restriction?

Thanks,
Jon

On Apr 13, 2017, at 1:24 PM, Ben Cohen via swift-evolution < >> swift-evolution@swift.org> wrote:

Online copy here:

GitHub - airspeedswift/swift-evolution at fa007138a54895e94d22e053122ca24ffa0b2eeb
38a54895e94d22e053122ca24ffa0b2eeb/proposals/NNNN-ComparisonReform.md

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Such a design would also require every port of numeric algorithms that use
the concrete types themselves (Float and Double) to use .isEqualTo, etc.,
or else silently break. It would make Swift a non-starter for almost any
serious numeric work that uses floating point types.

···

On Sat, Apr 15, 2017 at 5:25 PM, Karl Wagner via swift-evolution < swift-evolution@swift.org> wrote:

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.
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.

Yeah, I think it should really be considered. An IEEEComparable protocol,
defining methods such as “isEqualTo”, “isLessThan”, etc would make the
distinction clearer.

And, as you said, not many people really know or care about the IEEE
comparison special cases. It’s a bit of an expert field. Is it that bad to
ask experts who explicitly care about the order between +/- 0 or different
NaNs to use a special function for that behaviour?

I suppose it needs to be weighed up with something like: (number of people
ignorant of the difference) x (probability of unexpected result) x
(significance of unexpected result, if estimable).

How often do non-floating point experts even use FloatingPoint as a
protocol (as opposed to Float/Double)?

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.
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.

Yeah, I think it should really be considered. An IEEEComparable protocol,
defining methods such as “isEqualTo”, “isLessThan”, etc would make the
distinction clearer.

And, as you said, not many people really know or care about the IEEE
comparison special cases. It’s a bit of an expert field. Is it that bad to
ask experts who explicitly care about the order between +/- 0 or different
NaNs to use a special function for that behaviour?

Let's be clear, currently `+0.0 == -0.0`, and this is very important. With
this proposal `+0.0 != 0.0` in the generic context, which has useful
properties, but is not what you want when working with the concrete type
Double.

Also, currently, `.nan != .nan`, which is also very important. It is not
about "different types of NaN," as even the same type of NaN compares
unequal to itself. Some very basic numeric algorithms that operate on
concrete FP types expect NaN not to equal anything. With this proposal
`.nan == .nan` in the generic context, which is useful for generic sorting
but not what you want when working with Double.

···

On Sat, Apr 15, 2017 at 5:25 PM, Karl Wagner via swift-evolution < swift-evolution@swift.org> wrote:

I suppose it needs to be weighed up with something like: (number of people
ignorant of the difference) x (probability of unexpected result) x
(significance of unexpected result, if estimable).

How often do non-floating point experts even use FloatingPoint as a
protocol (as opposed to Float/Double)?

- Karl

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

> 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 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.

Yup.

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.

Yep.

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`.

Hey, it's your funeral! ;-)

If this proposal is implemented, *I* would be responsible for making
XCTest Rational-aware, vending `XCTAssertEqual<Rational<T>>(_:_:)`.

Now you're mixing apples and oranges with NaN and +/-0, the latter of
which doesn't apply to rationals. The way you make XCTest aware of NaN
is to do the same thing that floating point did: vend the static version
of == separately from the dynamic version, using @_implements (and get
us to drop the underscore).

XCTestAssertEqual is a perfect example of a generic function: it is (or
should be!) concerned with verifying that results match expectations,
not that “==” behaves according to the specific quirks of a type.
Anything else would make

   XCTestAssertEqual(resultOfComputation, NaN)

meaningless.

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.

I'm sorry, I don't follow. Give me an example, please, and also clearly
define “FP-awareness.”

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.)

Either way, we either end up documenting @_implements or special magic
behavior for floating point types. Somebody already needs to document
the latter for floating point types (.NaN != .NaN), frankly, even though IEEE is a
commonly implemented standard.

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,

Agreed, it will require learning something new.

and that this feature does not even currently exist.

I don't understand the relevance of that last point though.

> 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?

I don't know what you mean by “blows up.” If you mean, “traps,” I think
that's fine. If you mean, “misbehaves,” I think that's really hard to
justify unless it's easy for users to keep these values from ever
entering their program. The algorithm itself doesn't know all the types
on which it can operate, so its documentation isn't in a position to
warn users about this precondition. Therefore, the caller, who got the
values from somewhere and may be generic itself, is in no position to
check for the problem. The only cure is to bottleneck production of
values of that type and ensure that those values never get beyond some
boundary in the code. I don't think that's practical in the case of
floating point, not least because ordinary operations on legit values
can produce the problematic values.

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.

I'd rather not, myself. The choices are not good:

1. Complexity and occasional surprises that can be documented and
   explained.
2. Silent misbehavior.
3. Separate syntax for distinct semantics.

#2 is the only one I find completely unacceptable.

> 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.

It's not a horrible idea, but to be fair it's not really a design yet,
either. You haven't said anything about what it's supposed to mean, how
it is supposed to be used, how people write, use, and compose generic
algorithms with it, how it deals with floating point, etc.

···

on Sat Apr 15 2017, Xiaodi Wu <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:

--
-Dave

One benefit of the idea of using comparison metrics instead of changing
comparable, is that you could just have two metrics on double. The default
one which is closer to what we have now (or the new thing Xiaodi suggested
with trapping NaN), and one which matches IEEE. You could in fact make a
metric for each level if desired.

Right, but I'm arguing that having multiple comparison metrics is
_undesirable_. I see the need for one design that can accommodate unordered
comparisons. I do not see the need for a second comparison metric.

···

On Sun, Apr 16, 2017 at 12:35 PM, Jonathan Hull via swift-evolution < swift-evolution@swift.org> wrote:

Thanks,
Jon

On Apr 13, 2017, at 8:30 PM, Jonathan Hull via swift-evolution < > swift-evolution@swift.org> wrote:

One more thought. I am generally in favor of this proposal, but since it
is in the pitch stage, I wanted to offer an alternative approach that I
didn’t see mentioned. Food for thought/discussion.

What if, instead of repurposing comparable (and adding new functions for
options like case insensitive compare), we define a comparison metric (with
all of the options built in) and then use that to get our comparison
result. Comparable things would have a default metric that uses ‘<‘ and
‘==‘ to provide a comparison result.

The metric would have a method which takes two things and returns a
ComparisonResult. The two things would usually be the same type, but
wouldn’t necessarily have to be.

As a convenience, any type could have a compared(to:, using:) method where
you pass a comparison metric to the using parameter and receive a
ComparisonResult. Comparable things could add a compared(with:) method and
the spaceship operator <=>, which both use the default metric.

Pros:
• Would work without compiler alterations
• You can create metrics that compare items of different types
• Can setup the metric once for algorithms/comparisons with high setup cost
• Things like 'compare(to: other, using: .caseInsensitiveComparison)' fall
out of the design without having to create/learn various different versions
of compare on different types.
• Spaceship operator <=> for those who want it
• In some cases, it can provide a much more efficient implementation based
on underlying structure. For example, you can get a metric from
String/Unicode which is optimized for a particular view of that string (say
ASCII). Depending on the case, when one of the objects doesn’t match the
optimized type, it can either convert or fallback to a more general
algorithm… but it should provide a pretty big win when most of the objects
have a known structure.

Cons:
• More protocols defined by the design
• Requires an extra struct/class to implement in non-standard cases (e.g.
case insensitive compare)
• Probably something else I am missing

Thanks,
Jon

On Apr 13, 2017, at 1:24 PM, Ben Cohen via swift-evolution < > swift-evolution@swift.org> wrote:

Online copy here:

https://github.com/airspeedswift/swift-evolution/blob/
fa007138a54895e94d22e053122ca24ffa0b2eeb/proposals/NNNN-
ComparisonReform.md

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Oh come on. You are exaggerating the issue here. As I said, there isn’t really any extra maintenance needed and people are already familiar with their meaning. It isn’t like they have to learn new archaic symbols. Even HTML defines a special code for them because their use is common. There is only one reasonable implementation, and they can’t have another meaning without causing confusion. They are pretty easy to type on most keyboards.

I view the gain in clarity/readability as functionality. Using ‘+’ instead of ‘adding(to:)’ may not have extra functionality by your definition, but it sure makes long equations easier for me to parse. This is something that should have been there since the beginning. I know it isn’t a high priority, but since we are re-writing the protocol anyway, we should take the 3 seconds it takes to add them and do it. I am happy to provide the implementations if needed.

Thanks,
Jon

···

On Apr 13, 2017, at 8:09 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Thu, Apr 13, 2017 at 7:51 PM, Jonathan Hull <jhull@gbis.com <mailto:jhull@gbis.com>> wrote:
I think “vastly” is vastly overstating it, especially since they are not customization points… merely aliases.

It increases the number of methods on Comparable from 5 to 8, and it increases the number of standard operators (which will already expand if one-sided ranges are approved), with no functionality gain whatsoever. It doubles the number of ways to write three operators. And it expands the standard operators past the ASCII range, which was a choice deliberately not taken during the debate about how to name SetAlgebra members. If this is not a vast expansion, I do not know what is.

There is nothing else those operators could do without causing confusion. Swift favors clarity, and these operators are much more clear (which I count as a benefit). Also ‘<=‘ looks like an arrow, which I find very distracting in code, as my eye wants to follow it.

I do use this myself in application code, but I can’t add it to my framework code without potentially clashing with others (or myself) who have added the same behavior for themselves. Even though the implementations are exactly the same, it becomes ambiguous which of the (identical) definitions should be used. Having it in the library would mean that everyone would just use that version (and there is only one reasonable implementation, so it wont limit anyone).

If it were in high demand, a de-facto standard would have arisen by now vending these symbols as a standalone library. For now, nothing stops you from defining them for internal use in your library.

I really don’t think there is danger of harm here as you seem to be implying. Anyone who sees ‘≤’ will know what it means, even if they aren’t familiar with Swift. If the implementations point to ‘<=‘, etc… then nothing will get out of sync. There really isn’t any extra maintenance needed.

Thanks,
Jon

On Apr 13, 2017, at 5:03 PM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:

On Thu, Apr 13, 2017 at 7:02 PM, Jonathan Hull via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
This is a sugar request, but if we are rearranging these things anyway, can we *please* add the ‘≤’, ‘≥’, and ‘≠’ operators to comparable. I know they would do the same thing as ‘<=‘, ‘>=‘, and ‘!=‘, but they can’t really be used for anything else without being confusing (because they literally have that meaning in mathematics), and they make my code so much more readable.

This is vastly increasing API surface area for every user of Swift for literally no functionality. Why isn't it sufficient that Swift allows you to do this for yourself without restriction?

Thanks,
Jon

On Apr 13, 2017, at 1:24 PM, Ben Cohen via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Online copy here:

https://github.com/airspeedswift/swift-evolution/blob/fa007138a54895e94d22e053122ca24ffa0b2eeb/proposals/NNNN-ComparisonReform.md

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Oh come on. You are exaggerating the issue here. As I said, there isn’t
really any extra maintenance needed and people are already familiar with
their meaning. It isn’t like they have to learn new archaic symbols. Even
HTML defines a special code for them because their use is common. There is
only one reasonable implementation, and they can’t have another meaning
without causing confusion. They are pretty easy to type on most keyboards.

I view the gain in clarity/readability as functionality.

It is not a gain but a loss in clarity, because it introduces two spellings
for the same thing where now there is one.

Using ‘+’ instead of ‘adding(to:)’ may not have extra functionality by your

definition, but it sure makes long equations easier for me to parse.

Again, _instead of_ is not the problem, but _in addition to_ is. We have
one way to spell less-than-or-equal-to in the standard library, and it is
`<=`. That goes again to the "opinionated" part of Swift language design.

The trampolining design that was required for `FloatingPoint` (with methods
like `dividing(by:)`, etc.) was an unfortunate artifact of that protocol
being approved before static operators; those methods were once necessary
for functionality and today are a wart that should not be emulated.

This is something that should have been there since the beginning. I know

···

On Thu, Apr 13, 2017 at 11:00 PM, Jonathan Hull <jhull@gbis.com> wrote:

it isn’t a high priority, but since we are re-writing the protocol anyway,
we should take the 3 seconds it takes to add them and do it. I am happy to
provide the implementations if needed.

Thanks,
Jon

On Apr 13, 2017, at 8:09 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Thu, Apr 13, 2017 at 7:51 PM, Jonathan Hull <jhull@gbis.com> wrote:

I think “vastly” is vastly overstating it, especially since they are not
customization points… merely aliases.

It increases the number of methods on Comparable from 5 to 8, and it
increases the number of standard operators (which will already expand if
one-sided ranges are approved), with no functionality gain whatsoever. It
doubles the number of ways to write three operators. And it expands the
standard operators past the ASCII range, which was a choice deliberately
not taken during the debate about how to name SetAlgebra members. If this
is not a vast expansion, I do not know what is.

There is nothing else those operators could do without causing confusion.

Swift favors clarity, and these operators are much more clear (which I
count as a benefit). Also ‘<=‘ looks like an arrow, which I find very
distracting in code, as my eye wants to follow it.

I do use this myself in application code, but I can’t add it to my
framework code without potentially clashing with others (or myself) who
have added the same behavior for themselves. Even though the
implementations are exactly the same, it becomes ambiguous which of the
(identical) definitions should be used. Having it in the library would
mean that everyone would just use that version (and there is only one
reasonable implementation, so it wont limit anyone).

If it were in high demand, a de-facto standard would have arisen by now
vending these symbols as a standalone library. For now, nothing stops you
from defining them for internal use in your library.

I really don’t think there is danger of harm here as you seem to be

implying. Anyone who sees ‘≤’ will know what it means, even if they aren’t
familiar with Swift. If the implementations point to ‘<=‘, etc… then
nothing will get out of sync. There really isn’t any extra maintenance
needed.

Thanks,
Jon

On Apr 13, 2017, at 5:03 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Thu, Apr 13, 2017 at 7:02 PM, Jonathan Hull via swift-evolution < >> swift-evolution@swift.org> wrote:

This is a sugar request, but if we are rearranging these things anyway,
can we *please* add the ‘≤’, ‘≥’, and ‘≠’ operators to comparable. I know
they would do the same thing as ‘<=‘, ‘>=‘, and ‘!=‘, but they can’t really
be used for anything else without being confusing (because they literally
have that meaning in mathematics), and they make my code so much more
readable.

This is vastly increasing API surface area for every user of Swift for
literally no functionality. Why isn't it sufficient that Swift allows you
to do this for yourself without restriction?

Thanks,
Jon

On Apr 13, 2017, at 1:24 PM, Ben Cohen via swift-evolution < >>> swift-evolution@swift.org> wrote:

Online copy here:

GitHub - airspeedswift/swift-evolution at fa007138a54895e94d22e053122ca24ffa0b2eeb
38a54895e94d22e053122ca24ffa0b2eeb/proposals/NNNN-ComparisonReform.md

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

>
>>
>>
>> > 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 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.

Yup.

> 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.

Yep.

> 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`.

Hey, it's your funeral! ;-)

> If this proposal is implemented, *I* would be responsible for making
> XCTest Rational-aware, vending `XCTAssertEqual<Rational<T>>(_:_:)`.

Now you're mixing apples and oranges with NaN and +/-0, the latter of
which doesn't apply to rationals. The way you make XCTest aware of NaN
is to do the same thing that floating point did: vend the static version
of == separately from the dynamic version, using @_implements (and get
us to drop the underscore).

XCTestAssertEqual is a perfect example of a generic function: it is (or
should be!) concerned with verifying that results match expectations,
not that “==” behaves according to the specific quirks of a type.
Anything else would make

   XCTestAssertEqual(resultOfComputation, NaN)

meaningless.

Hmm, I will admit I have not tried it because I have always assumed that
the assertion above is meaningless, and that it ought to be.
`XCTAssertTrue(resultOfComputation.isNaN)` is the way I've been testing my
NaN's.

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.

I'm sorry, I don't follow. Give me an example, please, and also clearly
define “FP-awareness.”

By FP awareness, I mean that in comparing two FP values a function uses
`FloatingPoint.==` as opposed to `Comparable.==`. For example, I expect
`XCTAssertEqual<T : FloatingPoint>(_:_:)` to be vended as part of XCTest,
in order to make sure that `XCTAssertEqual(resultOfComputation,
Double.nan)` always fails.

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.)

Either way, we either end up documenting @_implements or special magic
behavior for floating point types. Somebody already needs to document
the latter for floating point types (.NaN != .NaN), frankly, even though
IEEE is a
commonly implemented standard.

> 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,

Agreed, it will require learning something new.

> and that this feature does not even currently exist.

I don't understand the relevance of that last point though.

Not critical to the argument. Just that it will impact all users of Swift.
It's not as though some particularly studious user of Swift 3 will be
rewarded with a head start because he or she has devoted time to careful
study of the language.

>
>
>> > 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?

I don't know what you mean by “blows up.” If you mean, “traps,” I think
that's fine. If you mean, “misbehaves,” I think that's really hard to
justify unless it's easy for users to keep these values from ever
entering their program. The algorithm itself doesn't know all the types
on which it can operate, so its documentation isn't in a position to
warn users about this precondition. Therefore, the caller, who got the
values from somewhere and may be generic itself, is in no position to
check for the problem. The only cure is to bottleneck production of
values of that type and ensure that those values never get beyond some
boundary in the code. I don't think that's practical in the case of
floating point, not least because ordinary operations on legit values
can produce the problematic values.

>> 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.

I'd rather not, myself. The choices are not good:

1. Complexity and occasional surprises that can be documented and
   explained.
2. Silent misbehavior.
3. Separate syntax for distinct semantics.

#2 is the only one I find completely unacceptable.

>> > 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.

It's not a horrible idea, but to be fair it's not really a design yet,
either. You haven't said anything about what it's supposed to mean, how
it is supposed to be used, how people write, use, and compose generic
algorithms with it, how it deals with floating point, etc.

I've evolved my thinking based on the discussion. Let's see:

public enum ComparisonResult : Equatable {
  case orderedAscending, equivalent, orderedDescending, unordered
  // I have renamed one case, in the hopes of emphasizing that two values
  // that compare `equivalent` are not merely ordered the same, but should
  // satisfy the three conditions of an equivalence relation.
}
// I will have to leave the question of how to bridge from Obj-C
// NSComparisonResult up to more capable hands.

public protocol Comparable : Equatable {
  func compared(to other: Self) -> ComparisonResult
}
// This will have to be modified as necessarily to support compiler magic
// necessary for source-compatibility with Swift 3

extension Comparable {
  public static func == (lhs: Self, rhs: Self) -> Bool {
    let comparison = lhs.compared(to: rhs)
    precondition(comparison != .unordered)
    return comparison == .equivalent
  }

  public static func < (lhs: Self, rhs: Self) -> Bool {
    let comparison = lhs.compared(to: rhs)
    precondition(comparison != .unordered)
    return comparison == .orderedAscending
  }
  // etc.

  // Something I thought I'd never want to see, but on reflection not
terrible:
  public static func &== (lhs: Self, rhs: Self) -> Bool {
    return lhs.compared(to: rhs) == .equivalent
  }

  public static func &< (lhs: Self, rhs: Self) -> Bool {
    return lhs.compared(to: rhs) == .orderedAscending
  }
  // etc.
}

extension FloatingPoint : Comparable {
  public func compared(to other: Self) -> ComparisonResult {
    if isNaN || other.isNaN { return .unordered }
    if isLess(than: other) { return .orderedAscending }
    if other.isLess(than: self) { return .orderedDescending }

    // On reconsideration, probably actually least surprising if +0.0 ==
-0.0
    // no matter what. It does not matter that division can give different
    // results for two equivalent values, only that the three rules of an
    // equivalence relation will hold, and it does.
    //
    // If a user is really savvy to the sign of zero, there is
FloatingPoint.sign,
    // which is necessary in any case for working with FP operations that
    // distinguish between +0 and -0. I can't think of a generic algorithm
over
    // all Comparable types that could make use of the distinction between
+0
    // and -0.
    return .equivalent
  }
}

In this design, `==`, `<`, etc. correspond to IEEE "signaling" operators,
while `&==`, `&<`, etc. correspond to C-style operators, which IEEE calls
"quiet" and actually notates using "?==", "?<", etc.

···

On Sat, Apr 15, 2017 at 10:32 PM, Dave Abrahams via swift-evolution < swift-evolution@swift.org> wrote:

on Sat Apr 15 2017, Xiaodi Wu <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:

One benefit of the idea of using comparison metrics instead of changing comparable, is that you could just have two metrics on double. The default one which is closer to what we have now (or the new thing Xiaodi suggested with trapping NaN), and one which matches IEEE. You could in fact make a metric for each level if desired.

Right, but I'm arguing that having multiple comparison metrics is _undesirable_. I see the need for one design that can accommodate unordered comparisons. I do not see the need for a second comparison metric.

That is why there is a default metric. For most uses, what you suggest (i.e. trapping on NaN) makes sense, and that would be the default. Someone may have a need to compare in a way which differentiates NaN or matches a different IEEE level, and they can create a metric that provides that comparison, and still be able to use it with all of the algorithms in the standard library. (Note: I see those alternate metrics as living in a library as opposed to the standard library)

It is clear that we will need multiple comparison metrics for some types (e.g. Case/Diacritic insensitive compare), and I am suggesting we formalize that so we don’t end up with a bunch of random ‘compare(with: optionA: optionB:)’ functions which are incompatible across types.

Thanks,
Jon

···

On Apr 16, 2017, at 10:45 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:
On Sun, Apr 16, 2017 at 12:35 PM, Jonathan Hull via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Apr 13, 2017, at 8:30 PM, Jonathan Hull via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

One more thought. I am generally in favor of this proposal, but since it is in the pitch stage, I wanted to offer an alternative approach that I didn’t see mentioned. Food for thought/discussion.

What if, instead of repurposing comparable (and adding new functions for options like case insensitive compare), we define a comparison metric (with all of the options built in) and then use that to get our comparison result. Comparable things would have a default metric that uses ‘<‘ and ‘==‘ to provide a comparison result.

The metric would have a method which takes two things and returns a ComparisonResult. The two things would usually be the same type, but wouldn’t necessarily have to be.

As a convenience, any type could have a compared(to:, using:) method where you pass a comparison metric to the using parameter and receive a ComparisonResult. Comparable things could add a compared(with:) method and the spaceship operator <=>, which both use the default metric.

Pros:
• Would work without compiler alterations
• You can create metrics that compare items of different types
• Can setup the metric once for algorithms/comparisons with high setup cost
• Things like 'compare(to: other, using: .caseInsensitiveComparison)' fall out of the design without having to create/learn various different versions of compare on different types.
• Spaceship operator <=> for those who want it
• In some cases, it can provide a much more efficient implementation based on underlying structure. For example, you can get a metric from String/Unicode which is optimized for a particular view of that string (say ASCII). Depending on the case, when one of the objects doesn’t match the optimized type, it can either convert or fallback to a more general algorithm… but it should provide a pretty big win when most of the objects have a known structure.

Cons:
• More protocols defined by the design
• Requires an extra struct/class to implement in non-standard cases (e.g. case insensitive compare)
• Probably something else I am missing

Thanks,
Jon

On Apr 13, 2017, at 1:24 PM, Ben Cohen via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Online copy here:

https://github.com/airspeedswift/swift-evolution/blob/fa007138a54895e94d22e053122ca24ffa0b2eeb/proposals/NNNN-ComparisonReform.md

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

One benefit of the idea of using comparison metrics instead of changing
comparable, is that you could just have two metrics on double. The default
one which is closer to what we have now (or the new thing Xiaodi suggested
with trapping NaN), and one which matches IEEE. You could in fact make a
metric for each level if desired.

Right, but I'm arguing that having multiple comparison metrics is
_undesirable_. I see the need for one design that can accommodate unordered
comparisons. I do not see the need for a second comparison metric.

That is why there is a default metric. For most uses, what you suggest
(i.e. trapping on NaN) makes sense, and that would be the default. Someone
may have a need to compare in a way which differentiates NaN or matches a
different IEEE level, and they can create a metric that provides that
comparison, and still be able to use it with all of the algorithms in the
standard library. (Note: I see those alternate metrics as living in a
library as opposed to the standard library)

FloatingPoint already exposes IEEE total ordering as `isTotallyOrdered`.
You can use it in `sort(by:)`. That's not what I'm talking about.

This proposal is about the design of `Comparable`. My concern is about `<`
giving different answers depending on surrounding code. I don't see the
point of it. Do you?

It is clear that we will need multiple comparison metrics for some types
(e.g. Case/Diacritic insensitive compare), and I am suggesting we formalize
that so we don’t end up with a bunch of random ‘compare(with: optionA:
optionB:)’ functions which are incompatible across types.

Since the String overhaul is not done, and since localized comparison has
been explicitly deferred from even the current scope of the String
overhaul, I don't see how we can design around this with any sort of
insight.

In any case, again, I'm speaking specifically about the proposed design of
`Comparable`. Do you think that there are meaningful generic algorithms to
be written over localized string comparison and floating point comparison
which are not possible today, which requires a redesign of `Comparable`?

···

On Sun, Apr 16, 2017 at 12:58 PM, Jonathan Hull <jhull@gbis.com> wrote:

On Apr 16, 2017, at 10:45 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:
On Sun, Apr 16, 2017 at 12:35 PM, Jonathan Hull via swift-evolution < > swift-evolution@swift.org> wrote:

Thanks,
Jon

On Apr 13, 2017, at 8:30 PM, Jonathan Hull via swift-evolution < >> swift-evolution@swift.org> wrote:

One more thought. I am generally in favor of this proposal, but since it
is in the pitch stage, I wanted to offer an alternative approach that I
didn’t see mentioned. Food for thought/discussion.

What if, instead of repurposing comparable (and adding new functions for
options like case insensitive compare), we define a comparison metric (with
all of the options built in) and then use that to get our comparison
result. Comparable things would have a default metric that uses ‘<‘ and
‘==‘ to provide a comparison result.

The metric would have a method which takes two things and returns a
ComparisonResult. The two things would usually be the same type, but
wouldn’t necessarily have to be.

As a convenience, any type could have a compared(to:, using:) method
where you pass a comparison metric to the using parameter and receive a
ComparisonResult. Comparable things could add a compared(with:) method and
the spaceship operator <=>, which both use the default metric.

Pros:
• Would work without compiler alterations
• You can create metrics that compare items of different types
• Can setup the metric once for algorithms/comparisons with high setup
cost
• Things like 'compare(to: other, using: .caseInsensitiveComparison)'
fall out of the design without having to create/learn various different
versions of compare on different types.
• Spaceship operator <=> for those who want it
• In some cases, it can provide a much more efficient implementation
based on underlying structure. For example, you can get a metric from
String/Unicode which is optimized for a particular view of that string (say
ASCII). Depending on the case, when one of the objects doesn’t match the
optimized type, it can either convert or fallback to a more general
algorithm… but it should provide a pretty big win when most of the objects
have a known structure.

Cons:
• More protocols defined by the design
• Requires an extra struct/class to implement in non-standard cases (e.g.
case insensitive compare)
• Probably something else I am missing

Thanks,
Jon

On Apr 13, 2017, at 1:24 PM, Ben Cohen via swift-evolution < >> swift-evolution@swift.org> wrote:

Online copy here:

GitHub - airspeedswift/swift-evolution at fa007138a54895e94d22e053122ca24ffa0b2eeb
38a54895e94d22e053122ca24ffa0b2eeb/proposals/NNNN-ComparisonReform.md

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution