[swift-evolution] [pitch] Comparison Reform

Xiaodi Wu xiaodi.wu at gmail.com
Tue Apr 25 21:17:57 CDT 2017

On Tue, Apr 25, 2017 at 8:38 PM, Jonathan Hull <jhull at gbis.com> wrote:

> On Apr 25, 2017, at 5:25 PM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
> On Tue, Apr 25, 2017 at 6:53 PM, Jonathan Hull <jhull at gbis.com> wrote:
>> I just wanted to ask for more detail on why this is a non-starter (it
>> seems like most of my ideas are dismissed as “non-starters”, but I am
>> rarely given a detailed reason why).
>> Migration would be a renaming from ‘Double' to ‘Double?’, but it wouldn’t
>> be cosmetic.  It would free us to use a non-optional Double, where we can
>> guarantee the answer wouldn’t be NaN/nil.  We would, as you say, have
>> functions like ‘cos(Double?)->Double?’ which propagate the optional, but we
>> could also add a ‘cos(Double)->Double’ overload which guarantees an actual
>> result.  For something like Tan, we would only have the optional version
>> because the answer may actually be undefined, even when the input isn't.
> Leave aside how one might implement such a system for a moment. The first
> major issue is that your idea does not address the issues we're talking
> about here:
> We are debating, for instance, how to compare arrays with elements of type
> `Double`. In your design, the question remains how we would compare arrays
> with elements of type `Double?`. If the answer is that you cannot compare
> arrays of type `[Double?]`, then we have a problem, because that's the type
> that people will use when they ingest data that might contain NaN. Sure,
> they can unwrap each element before using their data, but they can also
> test each element with `isNaN` today. We are trying to *improve* on the
> user experience of comparing arrays of floating point values *without*
> checking if they contain NaN, not trying to take that feature away.
> It solves the main problem we are dealing with in Comparable because
> defining Comparable on non-optional Double (without possibility of NaN) is
> fairly simple.

Yes, but `Double` would not the currency type for floating point work.
Since any input might contain NaN, people will be working pervasively with
`Double?`. Some consideration as to how the type would have to be designed
(I will leave that as an exercise to the reader) would reveal that `Double`
would either have trapping arithmetic operators or no arithmetic operators
at all. Who would use such a type? And for what purpose? No, you've just
pushed the problem from Double to Double?, but the issue is unsolved, and
now we have two types where before we had one.

> You could compare an array of ‘Double?' in the same way you could compare
> an array of ‘Int?’ now.  It would work the same way optionals work
> everywhere else.

Right, but again, `Double?` would be the currency type for floating point
work. And you have still not told me how arrays of `Double?` elements would
work. Would two arrays of NaN compare equal? Would two NaNs compare equal?
If NaN == NaN, then we've got a problem. The starting point for this
discussion is that, when working with floating point types, NaN != NaN.
It's required by IEEE standards, and it's relied upon by users.

You would just test for a lack of numerical result by checking the optional
> in the usual Swift way: guard let, if let, etc...
> Your design also doesn't address the problem of how NaN should compare
> with NaN. Only now, you've pushed the problem to `Optional`. By design,
> every `Optional<T>.none` compares equal to every other `Optional<U>.none`
> (yes, even of different types).
> Yes, the one major difference in behavior, which I mentioned in my earlier
> email, is that we are replacing NaN != NaN with nil == nil.  Everything
> else should behave the same.  The part that saves us here, is that Swift
> forces you to explicitly consider the optional case.
> Basically, I am taking the spirit/intent of the law over the letter of
> it.  As far as I can tell, (quiet) NaN was originally designed to provide
> the functionality which we use optional for in Swift.  The NaN != NaN thing
> came out of limitations of the computers in the 1980’s because you needed a
> way to tell if something was NaN or not (and isNaN() didn’t exist yet).

I'll refer you to Steve Canon's answer on StackOverflow:

Bottom line is, whatever it's designed for, it's here to stay. I'm *not* on
any IEEE committee; I'm not qualified to design an alternative universe of
floating point types; and the Swift evolution mailing list (or even Swift
itself) is not the place to design one. I and others rely on Swift floating
point types to be IEEE-complaint, so that's where we start the discussion
here about Comparable.

> The programmer (mental) model would be that Swift Double just doesn’t have
> NaN, and anywhere where you would normally return NaN, you return nil
> instead.

Look, you can already use Double? today. There is no barrier to trying it
out for yourself. However, `Double?.none` doesn't mean the same thing as
`Double.nan`. The former indicates that _there is no value_; the latter
indicates that there _is_ a value, it's just _not a number_. Suppose I
parse a list of numbers into an array. If I ask for [Double].last and get
nil, it's telling me that _there are no elements in the array_. If I get
.nan, it's telling me that there *is* a value, it just wasn't a number. In
the former case, I'd do nothing. In the latter case, I might prompt the
user: this isn't a number!

> However, the property of using NaN’s bits to represent nil let’s us
> inter-op seamlessly with C and ObjC (or any other language’s) code.  They
> just treat it as a double with NaN as normal (including NaN != NaN) and we
> interface with it as ‘Double?'

I'm going to sound like a broken record, now. Whether floating point types
in Swift conform to IEEE standards is _not_ up for discussion, afaict;
that's simply a given. Now, around that constraint, we're trying to design
a revised Comparable protocol. Code written today for floating point work
expects NaN != NaN. That is just something that is and will forever be. We
break source compatibility if we change that.

This did not always work correctly, if I recall, but it does now and it's
> an intentional part of the design. However, NaN must compare not equal to
> every NaN. These could not be more different properties. It seems quite
> absurd on its face that we might want NaN to compare equal to a value of
> type `Optional<UIViewController>`.
> Is there an algorithm that requires NaN != NaN that couldn’t be reasonably
> rewritten to handle nil/optionals instead?

I don't need an algorithm to show you the problem. See this expression: `0
* Double.infinity == .infinity * 0` correctly evaluates to false. Zero
times infinity is simply not equal to infinity times zero. You're
suggesting a design where the result would be true, and that simply won't
fly, because it's just not true.

I haven’t been able to think of one. They are extremely similar, because
> they were designed for the same use-cases.  The examples you have given so
> far (e.g. cos(Double)->Double) would all be trivial to migrate.
> Thanks,
> Jon
> In short, it would actually make people consider conditions which result
>> in NaN because of Swift’s machinery which makes people consider nil.
>> It also allows non-optional Double to easily conform to Comparable, and
>> removes the gotchas around Collections…  Pretty big wins for a “cosmetic
>> rename”.  The only thing we lose is 'NaN != Nan' (because nil == nil), but
>> then algorithms that had relied on that would be forced to consider the
>> NaN/nil case explicitly because of the optionals.
>> It would also inter-op well with C/ObjC code by just having the compiler
>> overlay Double? for Double…
>> Thanks,
>> Jon
>> On Apr 16, 2017, at 11:44 AM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>> On Sun, Apr 16, 2017 at 1:14 PM, Jonathan Hull <jhull at gbis.com> wrote:
>>> On Apr 16, 2017, at 10:42 AM, Xiaodi Wu via swift-evolution <
>>> swift-evolution at swift.org> wrote:
>>> The point is that, when you manipulate two real numbers, sometimes there
>>> is no numeric result. You cannot simply wish this away with a new numeric
>>> type because it is not an artifact of _modeling_ real numbers but rather
>>> intrinsic to mathematics itself.
>>> I agree with the rest of what you said, but I have to disagree on this
>>> point.  What I think he is saying is that, in Swift, we really should be
>>> representing the NaN case as an optional instead of a magic value on the
>>> type itself (similar to how swift uses an optional instead of NSNotFound).
>>> In fact, that might be an actual option here.  For ‘Double?’ the
>>> compiler could use the bit pattern for NaN internally to represent .none (I
>>> believe it does similar tricks to save space with other optional types).
>>> Then disallow reference to NaN within swift code.  Functions or operations
>>> which could produce NaN would either have to produce an optional or trap in
>>> case of NaN. (e.g. the trig functions would likely return an optional, and
>>> 0/0 would trap).
>>> I think it would actually lead to much better code because the compiler
>>> would force you to have to explicitly deal with the case of optional/NaN
>>> when it is possible.  Migration would be tricky though...
>> This is essentially a cosmetic renaming from `Double` to `Double?`. There
>> are rules for propagating NaN which numeric algorithms expect. For example,
>> `cos(.nan)` returns a value. If your design is to work, every function that
>> takes a `Double` will need to take a `Double?`.
>> Just as Swift String conforms to Unicode standards, FloatingPoint
>> conforms to IEEE standards. You'd have to come up with enormous benefits to
>> justify breaking that. Doing so for Swift 4 is plainly a non-starter.
>> Thanks,
>>> Jon
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170425/7adfb127/attachment-0001.html>

More information about the swift-evolution mailing list