<div dir="ltr"><div class="gmail_quote"><div dir="ltr">On Fri, Jul 22, 2016 at 2:52 PM Dave Abrahams via swift-evolution &lt;<a href="mailto:swift-evolution@swift.org">swift-evolution@swift.org</a>&gt; wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><br>
on Fri Jul 22 2016, Tony Allevato &lt;<a href="mailto:swift-evolution@swift.org" target="_blank">swift-evolution@swift.org</a>&gt; wrote:<br>
<br>
&gt; I like a lot of this, but the changes to Equatable are where I get stuck.<br>
&gt; What are the scenarios where areSame is useful *outside* the context of the<br>
&gt; proposed new Comparable interface?<br>
&gt;<br>
&gt; I ask because changing the requirement for Equatable to areSame instead of<br>
&gt; == seems like a backwards change to me. There are plenty of unorderable<br>
&gt; types where == is the obvious thing you want to implement, and this makes<br>
&gt; it less obvious. It also adds a named method to a protocol to serve the<br>
&gt; purpose of an operator, which I&#39;ve been fighting hard against in SE-0091<br>
&gt; (even though you keep the global one and delegate to it).<br>
&gt;<br>
&gt; There are two concepts at play here: comparability and orderability. 99.99%<br>
&gt; of the time, they are identical.<br>
<br>
The concepts are “domain-specific semantics” vs “semantics that is<br>
useful in generic contexts.”  Yes, they are usually identical.<br>
<br>
&gt; Your proposal mentions one place where they&#39;re not: IEEE floating<br>
&gt; point numbers, because there exists an element in that space, NaN,<br>
&gt; that doesn&#39;t satisfy an equivalence relation at all.<br>
<br>
It&#39;s not limited to NaN.  The +0/-0 distinction can be tricky as well.<br>
<br>
&gt; But it&#39;s still reasonable to want a stable ordering with those<br>
&gt; included.<br>
<br>
It&#39;s also reasonable to want to search for those in a collection or use<br>
them as hash keys.  I&#39;m pointing this out because it goes to the<br>
definition of equality, which sorting in general does not.<br>
<br>
&gt; In the proposal as it&#39;s written right now, the individual inequality<br>
&gt; operators are implemented in terms of &lt;=&gt;. That won&#39;t work for<br>
&gt; FloatingPoint, because (NaN &lt; x) and (NaN &gt;= x) should both be false but<br>
&gt; the default implementations provided would make the latter true. So<br>
&gt; FloatingPoint would still have to provide its own implementations of *all<br>
&gt; of the (in)equality operators*, not just ==, in order to have the correct<br>
&gt; definition w.r.t. to IEEE 754. I didn&#39;t see that called out anywhere in the<br>
&gt; write-up.<br>
<br>
That&#39;s my error, actually. I wasn&#39;t thinking straight when I proposed a<br>
change to the proposal that I claimed dropped the need for the other<br>
operators.<br>
<br>
&gt; That being said, don&#39;t get me wrong—there&#39;s still a lot about this proposal<br>
&gt; that I like :)  Here&#39;s what I&#39;m thinking (which is mostly what you have<br>
&gt; written, with some tweaks):<br>
&gt;<br>
&gt; 1) Don&#39;t change Equatable. I don&#39;t see a need to distinguish between<br>
&gt; equivalence and equality on its own (if there is one, please let me<br>
&gt; know!).<br>
<br>
There is, because for algorithms that require Equatable to have any kind<br>
of meaningful semantics the equivalence relation requirement must be<br>
fulfilled, and prominent types exist whose `==` operator is not an<br>
equivalence relation.<br></blockquote><div><br></div><div>Thanks for the detailed reply, Dave—this and some of your earlier replies to this thread have helped me understand the use cases for this distinction. So the argument is that something like this:</div><div><br></div><div>    [ 1.0, -2.0, Double.NaN, 4.0 ].contains(Double.NaN)</div><div><br></div><div>should return true because the argument is in the same equivalence class as the element, even though NaN == NaN currently returns false? That seems totally reasonable, and I think i<span style="line-height:1.5">t would be very helpful for the proposal to specifically call out some of these scenarios—right now it focuses mostly on ordering, which led to my confusion.</span></div><div><br></div><div>To take this further, let&#39;s say I have a data structure where the elements are a type with multiple fields, and the ordering is determined by a single one of those fields (the &quot;key&quot;). Would a reasonable definition of equivalence in this model be one where a is equivalent to b if a.key == b.key, regardless of the values of the other fields, and equality is implemented by comparing all the fields? This is similar to how C++ STL&#39;s definition of equivalence for ordered collections falls out of the expression !(a &lt; b) &amp;&amp; !(b &lt; a), since you could conceivably implement &lt; and == with the same distinctions there.</div><div><br></div><div>I would be completely supportive of using `===` for this equivalence instead of areSame, based on your argument about identity. Would we need an escape hatch for people who absolutely need to know whether two instances occupy the same address? If I had to choose equivalence vs. same-address as the one to make more verbose, same-address seems like the obvious choice to me.</div><div><br></div><div><br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
&gt; As it stands today, I think the proposal &quot;leaks&quot; ordering concepts into<br>
&gt; Equatable when it shouldn&#39;t.<br>
<br>
I don&#39;t see any evidence for that, and I don&#39;t even believe you&#39;ve said<br>
anything here to support that point of view.<br>
<br>
&gt; 2) Comparable defines &lt;=&gt;, as proposed, but *also* defines &lt;, &gt;, &lt;=, &gt;=. A<br>
&gt; protocol extension provides defaults for &lt;, &gt;, &lt;=, &gt;=, ==, and !=<br>
&gt; implemented in terms of &lt;=&gt;. This lets most implementors of Comparable<br>
&gt; implement &lt;=&gt; and get everything else for free, but it also lets types<br>
&gt; replace individual operators with customized implementations (see #4 below)<br>
&gt; easily *within* the type (SE-0091).<br>
<br>
Check<br>
<br>
&gt; 3) Comparable should be documented to imply that the default behavior is to<br>
&gt; link the behavior of &lt;=&gt; to the individual comparisons, but that it can be<br>
&gt; changed, meaning that only &lt;=&gt; must define a total ordering and the<br>
&gt; individual comparison operators need not.<br>
<br>
Yes, the doc comments are missing from the proposal.<br>
<br>
&gt; 4) The very few types, like FloatingPoint, that need to provide<br>
&gt; domain-specific behavior to do the obvious/intended thing for users can and<br>
&gt; should override &lt;, &gt;, &lt;=, &gt;=, ==, and !=. This should be called out<br>
&gt; explicitly, and it would *not* affect ordering.<br>
<br>
Depends what you mean by “affect ordering.”  Clearly if you sort Floats<br>
using &lt; explicitly, it will have an effect.<br>
<br>
&gt; I think it&#39;s entirely reasonable to have (NaN == NaN) return false and<br>
&gt; (NaN != NaN) return true but (NaN &lt;=&gt; NaN) return .same without<br>
&gt; introducing another areSame concept, because the former is demanded by<br>
&gt; IEEE 754.  5) Algorithms that rely on a total order, like sorts, must<br>
&gt; be implemented in terms of &lt;=&gt;, not in terms of the individual<br>
&gt; operators, because of the possibility that the definitions can be<br>
&gt; severed above.<br>
<br>
But you&#39;re forgetting algorithms that require an equivalence relation,<br>
which is basically everything that&#39;s constrained to Equatable.<br>
<br>
&gt; As mentioned below, the one thing that a three-way comparison loses is the<br>
&gt; easy ability to pass &gt; instead of &lt; to reverse the ordering, but it&#39;s<br>
&gt; trivial to write a function that does this and I think it should be<br>
&gt; included as part of the proposal. Something like this (may be typos, I&#39;m<br>
&gt; writing it in Gmail):<br>
&gt;<br>
&gt; public func reverse&lt;C: Comparable&gt;(ordering: (C, C) -&gt; Ordering) -&gt; (C, C)<br>
&gt; -&gt; Ordering {<br>
&gt;   return { lhs, rhs in<br>
&gt;     switch ordering(lhs, rhs) {<br>
&gt;     case .ascending: return .descending<br>
&gt;     case .descending: return .ascending<br>
&gt;     case .same: return .same<br>
&gt;   }<br>
&gt; }<br>
&gt;<br>
&gt; (Comedy alternative: Add a second operator, &gt;=&lt;. But that might be pushing<br>
&gt; it.)<br>
<br>
Agreed, we should do something about this use case.<br>
<br>
--<br>
Dave<br>
<br>
_______________________________________________<br>
swift-evolution mailing list<br>
<a href="mailto:swift-evolution@swift.org" target="_blank">swift-evolution@swift.org</a><br>
<a href="https://lists.swift.org/mailman/listinfo/swift-evolution" rel="noreferrer" target="_blank">https://lists.swift.org/mailman/listinfo/swift-evolution</a><br>
</blockquote></div></div>