Parcourir la source

Rework operator interfaces (#1178)

Add concrete design for interfaces for comparison.

Rename interfaces for arithmetic following current thinking in #1058.

Update rules for mixed-type comparisons for data classes following #710.

Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
Richard Smith il y a 4 ans
Parent
commit
4a8ca9cd0f

+ 31 - 31
docs/design/expressions/arithmetic.md

@@ -182,77 +182,75 @@ following family of interfaces:
 
 
 ```
 ```
 // Unary `-`.
 // Unary `-`.
-interface Negatable {
+interface Negate {
   let Result:! Type = Self;
   let Result:! Type = Self;
-  fn Negate[me: Self]() -> Result;
+  fn Op[me: Self]() -> Result;
 }
 }
 ```
 ```
 
 
 ```
 ```
 // Binary `+`.
 // Binary `+`.
-interface AddableWith(U:! Type) {
+interface AddWith(U:! Type) {
   let Result:! Type = Self;
   let Result:! Type = Self;
-  fn Add[me: Self](other: U) -> Result;
+  fn Op[me: Self](other: U) -> Result;
 }
 }
-constraint Addable {
-  extends AddableWith(Self) where .Result = Self;
+constraint Add {
+  extends AddWith(Self) where .Result = Self;
 }
 }
 ```
 ```
 
 
 ```
 ```
 // Binary `-`.
 // Binary `-`.
-interface SubtractableWith(U:! Type) {
+interface SubWith(U:! Type) {
   let Result:! Type = Self;
   let Result:! Type = Self;
-  fn Subtract[me: Self](other: U) -> Result;
+  fn Op[me: Self](other: U) -> Result;
 }
 }
-constraint Subtractable {
-  extends SubtractableWith(Self) where .Result = Self;
+constraint Sub {
+  extends SubWith(Self) where .Result = Self;
 }
 }
 ```
 ```
 
 
 ```
 ```
 // Binary `*`.
 // Binary `*`.
-interface MultipliableWith(U:! Type) {
+interface MulWith(U:! Type) {
   let Result:! Type = Self;
   let Result:! Type = Self;
-  fn Multiply[me: Self](other: U) -> Result;
+  fn Op[me: Self](other: U) -> Result;
 }
 }
-constraint Multipliable {
-  extends MultipliableWith(Self) where .Result = Self;
+constraint Mul {
+  extends MulWith(Self) where .Result = Self;
 }
 }
 ```
 ```
 
 
 ```
 ```
 // Binary `/`.
 // Binary `/`.
-interface DividableWith(U:! Type) {
+interface DivWith(U:! Type) {
   let Result:! Type = Self;
   let Result:! Type = Self;
-  fn Divide[me: Self](other: U) -> Result;
+  fn Op[me: Self](other: U) -> Result;
 }
 }
-constraint Dividable {
-  extends DividableWith(Self) where .Result = Self;
+constraint Div {
+  extends DivWith(Self) where .Result = Self;
 }
 }
 ```
 ```
 
 
 ```
 ```
 // Binary `%`.
 // Binary `%`.
-interface ModuloWith(U:! Type) {
+interface ModWith(U:! Type) {
   let Result:! Type = Self;
   let Result:! Type = Self;
-  fn Mod[me: Self](other: U) -> Result;
+  fn Op[me: Self](other: U) -> Result;
 }
 }
-constraint Modulo {
-  extends ModuloWith(Self) where .Result = Self;
+constraint Mod {
+  extends ModWith(Self) where .Result = Self;
 }
 }
 ```
 ```
 
 
 Given `x: T` and `y: U`:
 Given `x: T` and `y: U`:
 
 
--   The expression `-x` is rewritten to `x.(Negatable.Negate)()`.
--   The expression `x + y` is rewritten to `x.(AddableWith(U).Add)(y)`.
--   The expression `x - y` is rewritten to
-    `x.(SubtractableWith(U).Subtract)(y)`.
--   The expression `x * y` is rewritten to
-    `x.(MultipliableWith(U).Multiply)(y)`.
--   The expression `x / y` is rewritten to `x.(DividableWith(U).Divide)(y)`.
--   The expression `x % y` is rewritten to `x.(ModuloWith(U).Mod)(y)`.
+-   The expression `-x` is rewritten to `x.(Negate.Op)()`.
+-   The expression `x + y` is rewritten to `x.(AddWith(U).Op)(y)`.
+-   The expression `x - y` is rewritten to `x.(SubWith(U).Op)(y)`.
+-   The expression `x * y` is rewritten to `x.(MulWith(U).Op)(y)`.
+-   The expression `x / y` is rewritten to `x.(DivWith(U).Op)(y)`.
+-   The expression `x % y` is rewritten to `x.(ModWith(U).Op)(y)`.
 
 
 Implementations of these interfaces are provided for built-in types as necessary
 Implementations of these interfaces are provided for built-in types as necessary
 to give the semantics described above.
 to give the semantics described above.
@@ -278,4 +276,6 @@ to give the semantics described above.
 ## References
 ## References
 
 
 -   Proposal
 -   Proposal
-    [#1083: arithmetic](https://github.com/carbon-language/carbon-lang/pull/1083).
+    [#1083: Arithmetic](https://github.com/carbon-language/carbon-lang/pull/1083)
+-   Proposal
+    [#1178: Rework operator interfaces](https://github.com/carbon-language/carbon-lang/pull/1178)

+ 1 - 1
docs/design/expressions/as_expressions.md

@@ -102,7 +102,7 @@ unordered with respect to binary arithmetic, bitwise operators, and unary `not`.
 
 
 ```
 ```
 // OK
 // OK
-var x: i32* as Comparable;
+var x: i32* as Eq;
 // OK, `x as (U*)` not `(x as U)*`.
 // OK, `x as (U*)` not `(x as U)*`.
 var y: auto = x as U*;
 var y: auto = x as U*;
 
 

+ 268 - 21
docs/design/expressions/comparison_operators.md

@@ -17,7 +17,11 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
     -   [Built-in comparisons and implicit conversions](#built-in-comparisons-and-implicit-conversions)
     -   [Built-in comparisons and implicit conversions](#built-in-comparisons-and-implicit-conversions)
         -   [Consistency with implicit conversions](#consistency-with-implicit-conversions)
         -   [Consistency with implicit conversions](#consistency-with-implicit-conversions)
         -   [Comparisons with constants](#comparisons-with-constants)
         -   [Comparisons with constants](#comparisons-with-constants)
-    -   [Overloading](#overloading)
+    -   [Extensibility](#extensibility)
+        -   [Equality](#equality)
+        -   [Ordering](#ordering)
+        -   [Compatibility of equality and ordering](#compatibility-of-equality-and-ordering)
+        -   [Custom result types](#custom-result-types)
     -   [Default implementations for basic types](#default-implementations-for-basic-types)
     -   [Default implementations for basic types](#default-implementations-for-basic-types)
 -   [Open questions](#open-questions)
 -   [Open questions](#open-questions)
 -   [Alternatives considered](#alternatives-considered)
 -   [Alternatives considered](#alternatives-considered)
@@ -43,6 +47,15 @@ Comparison operators all return a `bool`; they evaluate to `true` when the
 indicated comparison is true. All comparison operators are infix binary
 indicated comparison is true. All comparison operators are infix binary
 operators.
 operators.
 
 
+These operators have predefined meanings for some of Carbon's
+[built-in types](#built-in-comparisons-and-implicit-conversions), as well as for
+simple ["data" types](#default-implementations-for-basic-types) like structs and
+tuples.
+
+User-defined types can define the meaning of these operations by
+[implementing an interface](#extensibility) provided as part of the Carbon
+standard library.
+
 ## Details
 ## Details
 
 
 ### Precedence
 ### Precedence
@@ -222,29 +235,245 @@ literal that cannot be represented in `i32`. Such comparisons would always be
 tautological. This decision should be revisited if it proves problematic in
 tautological. This decision should be revisited if it proves problematic in
 practice, for example in templated code where the literal is sometimes in range.
 practice, for example in templated code where the literal is sometimes in range.
 
 
-### Overloading
+### Extensibility
+
+User-defined types can extend the behavior of the comparison operators by
+implementing interfaces. In this section, various properties are specified that
+such implementations "should" satisfy. These properties are not enforced in
+general, but the standard library might detect violations of some of them in
+some circumstances. These properties may be assumed by generic code, resulting
+in unexpected behavior if they are violated.
+
+#### Equality
+
+Comparison operators can be provided for user-defined types by implementing the
+`EqWith` and `OrderedWith` interfaces.
+
+The `EqWith` interface is used to define the semantics of the `==` and `!=`
+operators for a given pair of types:
+
+```
+interface EqWith(U:! Type) {
+  fn Equal[me: Self](u: U) -> bool;
+  default fn NotEqual[me: Self](u: U) -> bool {
+    return not (me == u);
+  }
+}
+constraint Eq {
+  extends EqWith(Self);
+}
+```
+
+Given `x: T` and `y: U`:
+
+-   The expression `x == y` calls `x.(EqWith(U).Equal)(y)`.
+-   The expression `x != y` calls `x.(EqWith(U).NotEqual)(y)`.
+
+```
+class Path {
+  private var drive: String;
+  private var path: String;
+  private fn CanonicalPath[me: Self]() -> String;
+
+  external impl as Eq {
+    fn Equal[me: Self](other: Self) -> bool {
+      return (me.drive, me.CanonicalPath()) ==
+             (other.drive, other.CanonicalPath());
+    }
+  }
+}
+```
+
+The `EqWith` overload is selected without considering possible implicit
+conversions. To permit implicit conversions in the operands of an `==` overload,
+the
+[`like` operator](/docs/design/generics/details.md#like-operator-for-implicit-conversions)
+can be used:
+
+```
+class MyInt {
+  var value: i32;
+  fn Value[me: Self]() -> i32 { return me.value; }
+}
+external impl i32 as ImplicitAs(MyInt);
+external impl like MyInt as EqWith(like MyInt) {
+  fn Equal[me: Self](other: Self) -> bool {
+    return me.Value() == other.Value();
+  }
+}
+fn CompareBothWays(a: MyInt, b: i32, c: MyInt) -> bool {
+  // OK, calls above implementation three times.
+  return a == a and a != b and b == c;
+}
+```
+
+The behavior of `NotEqual` can be overridden separately from the behavior of
+`Equal` to support cases like floating-point NaN values, where two values can
+compare neither equal nor not-equal, and thus both functions would return
+`false`. However, an implementation of `EqWith` should _not_ allow both `Equal`
+and `NotEqual` to return `true` for the same pair of values. Additionally, these
+operations should have no observable side-effects.
+
+```
+external impl like MyFloat as EqWith(like MyFloat) {
+  fn Equal[me: MyFloat](other: MyFloat) -> bool {
+    if (me.IsNaN() or other.IsNaN()) {
+      return false;
+    }
+    return me.Representation() == other.Representation();
+  }
+  fn NotEqual[me: MyFloat](other: MyFloat) -> bool {
+    if (me.IsNaN() or other.IsNaN()) {
+      return false;
+    }
+    return me.Representation() != other.Representation();
+  }
+}
+```
+
+Heterogeneous comparisons must be defined both ways around:
+
+```
+external impl like MyInt as EqWith(like MyFloat);
+external impl like MyFloat as EqWith(like MyInt);
+```
+
+**TODO:** Add an adapter to the standard library to make it easy to define the
+reverse comparison.
+
+#### Ordering
 
 
-Separate interfaces will be provided to permit overloading equality and
-relational comparisons. The exact design of those interfaces is left to a future
-proposal. As non-binding design guidance for such a proposal:
+The `OrderedWith` interface is used to define the semantics of the `<`, `<=`,
+`>`, and `>=` operators for a given pair of types.
 
 
--   The interface for equality comparisons should primarily provide the ability
-    to override the behavior of `==`. The `!=` operator can optionally also be
-    overridden, with a default implementation that returns `not (a == b)`. This
-    conversation was marked as resolved by chandlerc Show conversation
-    Overriding `!=` separately from `==` is expected to be used to support
-    floating-point NaN comparisons and for C++ interoperability.
+```
+choice Ordering {
+  Less,
+  Equivalent,
+  Greater,
+  Incomparable
+}
+interface OrderedWith(U:! Type) {
+  fn Compare[me: Self](u: U) -> Ordering;
+  default fn Less[me: Self](u: U) -> bool {
+    return me.Compare(u) == Ordering.Less;
+  }
+  default fn LessOrEquivalent[me: Self](u: U) -> bool {
+    let c: Ordering = me.Compare(u);
+    return c == Ordering.Less or c == Ordering.Equivalent;
+  }
+  default fn Greater[me: Self](u: U) -> bool {
+    return me.Compare(u) == Ordering.Greater;
+  }
+  default fn GreaterOrEquivalent[me: Self](u: U) -> bool {
+    let c: Ordering = me.Compare(u);
+    return c == Ordering.Greater or c == Ordering.Equivalent;
+  }
+}
+constraint Ordered {
+  extends OrderedWith(Self);
+}
+
+// Ordering.Less < Ordering.Equivalent < Ordering.Greater.
+// Ordering.Incomparable is incomparable with all three.
+external impl Ordering as Ordered;
+```
+
+**TODO:** Revise the above when we have a concrete design for enumerated types.
+
+Given `x: T` and `y: U`:
+
+-   The expression `x < y` calls `x.(OrderedWith(U).Less)(y)`.
+-   The expression `x <= y` calls `x.(OrderedWith(U).LessOrEquivalent)(y)`.
+-   The expression `x > y` calls `x.(OrderedWith(U).Greater)(y)`.
+-   The expression `x >= y` calls `x.(OrderedWith(U).GreaterOrEquivalent)(y)`.
 
 
--   The interface for relational comparisons should primarily provide the
-    ability to specify a three-way comparison operator. The individual
-    relational comparison operators can optionally be overridden separately,
-    with a default implementation in terms of the three-way comparison operator.
-    This facility is expected to be used primarily to support C++
-    interoperability.
+For example:
 
 
--   Overloaded comparison operators may wish to produce a type other than
-    `bool`, for uses such as a vector comparison producing a vector of `bool`
-    values. We should decide whether we wish to support such uses.
+```
+class MyWidget {
+  var width: i32;
+  var height: i32;
+
+  fn Size[me: Self]() -> i32 { return me.width * me.height; }
+
+  // Widgets are normally ordered by size.
+  external impl as Ordered {
+    fn Compare[me: Self](other: Self) -> Ordering {
+      return me.Size().(Ordered.Compare)(other.Size());
+    }
+  }
+}
+fn F(a: MyWidget, b: MyWidget) -> bool {
+  return a <= b;
+}
+```
+
+As for `EqWith`, the
+[`like` operator](/docs/design/generics/details.md#like-operator-for-implicit-conversions)
+can be used to permit implicit conversions when invoking a comparison, and
+heterogeneous comparisons must be defined both ways around:
+
+```
+fn ReverseOrdering(o: Ordering) -> Ordering {
+  return Ordering.Equivalent.(Ordered.Compare)(o);
+}
+external impl like MyInt as OrderedWith(like MyFloat);
+external impl like MyFloat as OrderedWith(like MyInt) {
+  fn Compare[me: Self](other: Self) -> Ordering {
+    return Reverse(other.(OrderedWith(Self).Compare)(me));
+  }
+}
+```
+
+The default implementations of `Less`, `LessOrEquivalent`, `Greater`, and
+`GreaterOrEquivalent` can be overridden if a more efficient version can be
+implemented. The behaviors of such overrides should follow those of the above
+default implementations, and the members of an `OrderedWith` implementation
+should have no observable side-effects.
+
+`OrderedWith` implementations should be _transitive_. That is, given `V:! Type`,
+`U:! OrderedWith(V)`, `T:! OrderedWith(U) & OrderedWith(V)`, `a: T`, `b: U`,
+`c: V`, then:
+
+-   If `a <= b` and `b <= c` then `a <= c`, and moreover if either `a < b` or
+    `b < c` then `a < c`.
+-   If `a >= b` and `b >= c` then `a >= c`, and moreover if either `a > b` or
+    `b > c` then `a > c`.
+-   If `a` and `b` are equivalent, then `a.Compare(c) == b.Compare(c)`.
+    Similarly, if `b` and `c` are equivalent, then
+    `a.Compare(b) == a.Compare(c)`.
+
+`OrderedWith` implementations should also be _consistent under reversal_. That
+is, given types `T` and `U` where `T is OrderedWith(U)` and
+`U is OrderedWith(T)`, and values `a: T` and `b: U`:
+
+-   If `a.(OrderedWith.Compare)(b)` is `Ordering.Greater`, then
+    `b.(OrderedWith.Compare)(a)` is `Ordering.Less`, and the other way around.
+-   Otherwise, `a.(OrderedWith.Compare)(b)` returns the same value as
+    `b.(OrderedWith.Compare)(a)`.
+
+There is no expectation that an `Ordered` implementation be a total order, a
+weak order, or a partial order, and in particular the implementation for
+floating-point types is none of these because NaN values do not compare less
+than or equivalent to themselves.
+
+**TODO:** The standard library should provide a way to specify that an ordering
+is a weak, partial, or total ordering, and a way to request such an ordering in
+a generic.
+
+#### Compatibility of equality and ordering
+
+There is no requirement that a pair of types that implements `OrderedWith` also
+implements `EqWith`. If a pair of types does implement both, however, the
+equality relation provided by `x.(EqWith.Equal)(y)` should be a refinement of
+the equivalence relation provided by
+`x.(OrderedWith.Compare)(y) == Ordering.Equivalent`.
+
+#### Custom result types
+
+**TODO:** Support a lower-level extensibility mechanism that allows a result
+type other than `bool`.
 
 
 ### Default implementations for basic types
 ### Default implementations for basic types
 
 
@@ -253,12 +482,27 @@ relational comparisons are also defined for all "data" types:
 
 
 -   [Tuples](../tuples.md)
 -   [Tuples](../tuples.md)
 -   [Struct types](../classes.md#struct-types)
 -   [Struct types](../classes.md#struct-types)
--   [Classes implementing an interface that identifies them as data classes.](../classes.md#interfaces-implemented-for-data-classes)
+-   [Classes implementing an interface that identifies them as data classes](../classes.md#interfaces-implemented-for-data-classes)
 
 
 Relational comparisons for these types provide a lexicographical ordering. In
 Relational comparisons for these types provide a lexicographical ordering. In
 each case, the comparison is only available if it is supported by all element
 each case, the comparison is only available if it is supported by all element
 types.
 types.
 
 
+Because implicit conversions between data classes can reorder fields, the
+implementations for data classes do not permit implicit conversions on their
+arguments in general. Instead:
+
+-   Equality comparisons are permitted between any two data classes that have
+    the same _unordered set_ of field names, if each corresponding pair of
+    fields has an `EqWith` implementation. Fields are compared in the order they
+    appear in the left-hand operand.
+-   Relational comparisons are permitted between any two data classes that have
+    the same _ordered sequence_ of field names, if each corresponding pair of
+    fields has an `OrderedWith` implementation. Fields are compared in order.
+
+Comparisons between tuples permit implicit conversions for either operand, but
+not both.
+
 ## Open questions
 ## Open questions
 
 
 The `bool` type should be treated as a choice type, and so should support
 The `bool` type should be treated as a choice type, and so should support
@@ -272,10 +516,13 @@ in general. That decision is left to a future proposal.
 -   [Convert operands like C++](/proposals/p0702.md#convert-operands-like-c)
 -   [Convert operands like C++](/proposals/p0702.md#convert-operands-like-c)
 -   [Provide a three-way comparison operator](/proposals/p0702.md#provide-a-three-way-comparison-operator)
 -   [Provide a three-way comparison operator](/proposals/p0702.md#provide-a-three-way-comparison-operator)
 -   [Allow comparisons as the operand of `not`](/proposals/p0702.md#allow-comparisons-as-the-operand-of-not)
 -   [Allow comparisons as the operand of `not`](/proposals/p0702.md#allow-comparisons-as-the-operand-of-not)
+-   [Rename `OrderedWith` to `ComparableWith`](/proposals/p1178.md#use-comparablewith-instead-of-orderedwith)
 
 
 ## References
 ## References
 
 
 -   Proposal
 -   Proposal
     [#702: Comparison operators](https://github.com/carbon-language/carbon-lang/pull/702)
     [#702: Comparison operators](https://github.com/carbon-language/carbon-lang/pull/702)
+-   Proposal
+    [#1178: Rework operator interfaces](https://github.com/carbon-language/carbon-lang/pull/1178)
 -   Issue
 -   Issue
     [#710: Default comparison for data classes](https://github.com/carbon-language/carbon-lang/issues/710)
     [#710: Default comparison for data classes](https://github.com/carbon-language/carbon-lang/issues/710)

+ 100 - 0
proposals/p1178.md

@@ -0,0 +1,100 @@
+# Rework operator interfaces
+
+<!--
+Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+Exceptions. See /LICENSE for license information.
+SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+-->
+
+[Pull request](https://github.com/carbon-language/carbon-lang/pull/1178
+
+<!-- toc -->
+
+## Table of contents
+
+-   [Problem](#problem)
+-   [Background](#background)
+-   [Proposal](#proposal)
+-   [Details](#details)
+-   [Rationale](#rationale)
+-   [Alternatives considered](#alternatives-considered)
+    -   [Use `ComparableWith` instead of `OrderedWith`](#use-comparablewith-instead-of-orderedwith)
+
+<!-- tocstop -->
+
+## Problem
+
+Our operator interface names need to be updated to match the decision in
+[#1058](https://github.com/carbon-language/carbon-lang/issues/1058). Further, we
+are missing a description of the interfaces used to overload comparison
+operators, and the rules are not up to date with the decision in
+[#710](https://github.com/carbon-language/carbon-lang/issues/710).
+
+## Background
+
+See the two leads issues for background and discussion of options.
+
+## Proposal
+
+See changes to the design.
+
+## Details
+
+Beyond establishing names for interfaces, this proposal also establishes:
+
+-   We will have high-level interfaces for equality and relational comparison.
+    The equality interface provides both `==` and `!=`. The relational
+    comparison interface provides all of `<`, `<=`, `>`, and `>=`.
+-   Following the convention established for arithmetic operators, we provide
+    both a heterogeneous comparison interface and a homogeneous constraint. For
+    example, `T is EqWith(T)` is equivalent to `T is Eq`.
+-   The high-level interfaces always return `bool`.
+-   The high-level interfaces have expected semantics associated with them.
+
+It is intended that we also provide low-level interfaces, to directly control
+individual operators and to allow a result type other than `bool`. These are not
+included in this proposal, as it's not yet clear how they should be specified,
+and it's more important to get the high-level interfaces decided at this point.
+
+## Rationale
+
+-   [Language tools and ecosystem](/docs/project/goals.md#language-tools-and-ecosystem)
+    -   High-level semantics allow tools to reason about the intended meaning of
+        Carbon code. For example, a tool could statically or dynamically
+        determine that an implementation of `Ordered` doesn't satisfy the
+        expected rules and produce a warning.
+-   [Performance-critical software](/docs/project/goals.md#performance-critical-software)
+    -   We expect `==` and ordering to be customized separately, in order to
+        avoid cases where a suboptimal `==` is constructed in terms of an
+        ordering. See
+        [C++ committee paper P1190R0](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1190r0.html)
+        for details on the problem.
+-   [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write)
+    -   Combining all comparison operators of the same kind -- equality or
+        relational -- into a single interface makes it both easier to implement
+        them and easier to write a generic constraint for them. This approach is
+        also expected to be easy to teach, with the low-level interfaces only
+        explained to a more advanced audience.
+-   [Practical safety and testing mechanisms](/docs/project/goals.md#practical-safety-and-testing-mechanisms)
+    -   While there are rules for the comparison interfaces, violating those
+        rules does not result in immediate unbounded undefined behavior.
+        However, implementations should still attempt to detect violations of
+        these rules and report them where that is feasible.
+-   [Interoperability with and migration from existing C++ code](/docs/project/goals.md#interoperability-with-and-migration-from-existing-c-code)
+    -   The intent to provide a low-level interface for individual operators is
+        directly motivated by the desire to provide strong interoperability with
+        operators defined in C++. While this functionality is not part of this
+        proposal, it's expected to follow once the interactions with generics
+        are worked out.
+
+## Alternatives considered
+
+### Use `ComparableWith` instead of `OrderedWith`
+
+We could use the term "comparable" for relational comparisons instead of
+"ordered". There is existing practice for both: for example, Rust and Haskell
+use `Ord`, and Swift uses `Comparable`.
+
+The main argument for using "ordered" instead of "comparable" is that `==` and
+`!=` are also a form of comparison but aren't part of `OrderedWith`, and the
+word "ordered" distinguishes relational comparison from equality comparison.