[Review] SE-0067: Enhanced Floating Point Protocols

The current design does not prevent bignum / arbitrary precision types. It prevents soft-float types with ridiculously large exponent ranges, but such enormous numbers are generally better modeled by either a logarithmic number system or level-index arithmetic.

Making exponent an associated type makes implementing any future heterogeneous operations (including generic conversions) more complex. This is a real cost, for a dubious theoretical benefit.

– Steve

···

On Apr 22, 2016, at 11:29 AM, David Sweeris <davesweeris@mac.com> wrote:

On Apr 22, 2016, at 9:56 AM, Stephen Canon via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Apr 22, 2016, at 10:54 AM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:

Naive question: is it necessary to make a trade-off here? Why not an
associated type Exponent that's Int for Float, Double, and Float80,
allowing for something else for bignums?

It’s an added (fairly minor) complexity to the API surface that confers approximately zero benefit.

So you admit the benefit isn’t *actually* zero? :-)

Yes, it does add a small bit of complexity to the API’s surface, but it's on a part of the surface that I don’t think people look at very much. How often are people extracting a float’s exponent? Plus, I suspect there’s a fair bit of overlap between the group of people who even know what `.exponent` would get used for, and those who’d get a warm fuzzy feeling from seeing that the standard library has baked-in, low-level support for bignum / arbitrary precision types.

Embrace the warm fuzzies… make exponent be an associated type.

- Dave Sweeris (who, despite the light-hearted tone, thinks this is actually pretty important)

It's not a number.

···

on Fri Apr 22 2016, Stephen Canon <scanon-AT-apple.com> wrote:

    This argument makes no sense to me. A floating point number is made up
    of three parts, and one part *is* not like the other ones.

In what way is it different?

--
Dave

Neither, actually. NaN is a valid value and even its use does not always
lead to exceptions. For example, it's possible (and a deliberate use case)
to assign NaN (even signaling NaN), and the IEEE 754 function maxNum(x, y)
(here proposed as `maximum`) will return x if x is not NaN and y is NaN.

···

On Fri, Apr 22, 2016 at 12:19 David Waite via swift-evolution < swift-evolution@swift.org> wrote:

On Apr 22, 2016, at 8:13 AM, Stephen Canon via swift-evolution < > swift-evolution@swift.org> wrote:

  /// A signaling NaN (not-a-number).
  @warn_unused_result
  static func signalingNaN: Self { get }

I’m not sure it really makes sense for a Bignum / APFloat type to support
such a value. But really I think this is just underspecified. What does it
mean, in terms of this protocol and its uses, for a NaN to be signaling? Is
it just a specific “color" of NaN, with no semantic requirements other than
being distinguishable?

I’m confused in that I haven’t really had exposure to signaling NaNs
before, and wasn’t really able to find hints on how they are used. Do math
operations create signaling NaNs, or is it an explicitly set value?

My first expectation was that you would either have overflow operators
where a NaN result would immediately raise an exception.

My second expectation if NaN was a valid value but use created exceptions
is that floating point numbers are monadic. Specifically, floating point
operations with soft NaN seem similar to optional, while Floats with
signaling NaN seem similar to implicitly unwrapped optionals - but without
the use of separate types to represent numbers and possibly-not-numbers.

That said, would use of an attribute on a floating point type, similar to
what is planned for IUOs, be appropriate to indicate that the type should
be treated as signaling?

-DW

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

At present, I would say that by default they should raise the invalid floating-point flag, which is impossible to observe from raw Swift, but is observable via the C stdlib <fenv.h> functions; they might optionally set a flag in an explicit inout parameter or trap. We should eventually have a specified floating-point environment model, but that’s (mostly) orthogonal to this API proposal.

– Steve

···

On Apr 25, 2016, at 2:12 PM, Jordan Rose <jordan_rose@apple.com> wrote:

On Apr 22, 2016, at 7:13, Stephen Canon <scanon@apple.com <mailto:scanon@apple.com>> wrote:

  /// A signaling NaN (not-a-number).
  @warn_unused_result
  static func signalingNaN: Self { get }

I’m not sure it really makes sense for a Bignum / APFloat type to support such a value. But really I think this is just underspecified. What does it mean, in terms of this protocol and its uses, for a NaN to be signaling? Is it just a specific “color" of NaN, with no semantic requirements other than being distinguishable?

There are a variety of means that a softfloat type could use to implement signaling NaNs. Here are two of the simpler ones:

(a) if running on HW with hard-float support, use native-precision hard-float instructions to set flags as needed.
(b) provide operation variants that take an inout flags / parameter:

  mutating func add(rhs: Self, inout flags: Flags)

Sorry, I think my question is more general than this. "Signaling" is a term of art in IEEE 754, but it doesn't actually mean anything in Swift, and none of the operations in the protocol have defined semantics for "signaling". Maybe that's the real question: what must each of the operations of FloatingPoint and BinaryFloatingPoint do when operating on an SNaN? a QNaN?

(I know what IEEE 754 says. We need the same rules in Swift semantics: "throws", "traps", "returns an unspecified result”.)

inits between different radices, while a feature we’d probably like to have eventually, are out of scope for this proposal.

The proposal does have:

  init<Source: BinaryFloatingPoint>(_ value: Source)

– Steve

···

On Apr 25, 2016, at 6:10 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

on Mon Apr 25 2016, Stephen Canon <swift-evolution@swift.org> wrote:

On Apr 23, 2016, at 8:53 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

A few things…

First, something absent: I'm a little bit concerned by the act that
there's no means to convert between different concrete FloatingPoint
types. Something like the IntMax mechanism in Swift 2's IntegerType
might be useful, though there may be good reasons not to do that.

There are concrete conversions between all built in float types.
Those aren’t going away. Are you saying that you want init(_ value:
Float) etc to be protocol requirements?

IIUC, what's being suggested is

init<T: FloatingPoint>(_ x: T)

I’m with Nicola on this one. Operators are currently odd in that they have to be declared globally. Everything else about protocol conformance is kept within the conforming type.

- Dave Sweeris

···

On Apr 26, 2016, at 9:28 AM, Tony Allevato via swift-evolution <swift-evolution@swift.org> wrote:

On Sun, Apr 24, 2016 at 2:57 AM Nicola Salmoria via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
> > func isEqual(to other: Self) ->Bool
> > func isLess(than other: Self) ->Bool
> > func isLessThanOrEqual(to other: Self) ->Bool
>
> I'm still not sure why these are methods instead of operators.

I think this is an *excellent* choice, and I hope it is the first step to completely removing operators from protocols.

IMHO throwing operators into protocols is inconsistent and confusing. Having regular methods and a single generic version of the operator that calls down on the type’s methods is clearer and guarantees that generic code can avoid ambiguities by calling the methods directly, instead of having to rely only on heavily overloaded global operators.

I personally disagree on this point. To me, a protocol describes a set of requirements for a type to fulfill, which includes things other than methods. Just as a protocol can define initializers, properties, and associated types that a type must define in order to conform, it makes sense that a protocol would also define which operators a conforming type must support.

Introducing a mapping between names and operators poses a few problems:

– IMO, they are overly verbose and add noise to the definition. This makes the language look less clean (I'm getting visions of NSDecimalNumber).
– They expose two ways to accomplish the same thing (writing `x.isEqual(to: y)` and `x == y`).
– Do certain operators automatically get mapped to method names with appropriate signatures across all types, or does a conforming type still have to provide that mapping by implementing the operators separately? If it's the latter, that's extra work for the author of the type writing the protocol. If it's the former, does it make sense to automatically push these operators for all types? Should any type that has an `add` method automatically get `+` as a synonym as well? That may not be desirable.

I'm very supportive of the floating-point protocol proposal in general, but I feel the arithmetic and comparison operations should be exposed by operators alone and not by methods, where there is a suitable operator that has the intended meaning.


Nicola

_______________________________________________
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
https://lists.swift.org/mailman/listinfo/swift-evolution

The main reasons to route through a single generic operator
implementation are:

* User experience; we want to cut down the number of overloads of any
  operator to a manageable set, in part because they live in the global
  namespace. When you look at a list of functions in the global
  namespace, seeing fifty instances of `func +` is not helpful.

* Type checker speed. Having all of these overloads around has
  historically put a strain on the type checker and made compilation
  slow. That may be less true today than it once was, though.

HTH,

···

on Tue Apr 26 2016, Tony Allevato <swift-evolution@swift.org> wrote:

On Sun, Apr 24, 2016 at 2:57 AM Nicola Salmoria via swift-evolution > <swift-evolution@swift.org> wrote:

    > > func isEqual(to other: Self) ->Bool
    > > func isLess(than other: Self) ->Bool
    > > func isLessThanOrEqual(to other: Self) ->Bool
    >
    > I'm still not sure why these are methods instead of operators.

    I think this is an *excellent* choice, and I hope it is the first step to
    completely removing operators from protocols.

    IMHO throwing operators into protocols is inconsistent and confusing. Having
    regular methods and a single generic version of the operator that calls down
    on the type’s methods is clearer and guarantees that generic code can avoid
    ambiguities by calling the methods directly, instead of having to rely only
    on heavily overloaded global operators.

I personally disagree on this point. To me, a protocol describes a set of
requirements for a type to fulfill, which includes things other than methods.
Just as a protocol can define initializers, properties, and associated types
that a type must define in order to conform, it makes sense that a protocol
would also define which operators a conforming type must support.

Introducing a mapping between names and operators poses a few problems:

– IMO, they are overly verbose and add noise to the definition. This makes the
language look less clean (I'm getting visions of NSDecimalNumber).
– They expose two ways to accomplish the same thing (writing `x.isEqual(to: y)`
and `x == y`).
– Do certain operators automatically get mapped to method names with appropriate
signatures across all types, or does a conforming type still have to provide
that mapping by implementing the operators separately? If it's the latter,
that's extra work for the author of the type writing the protocol. If it's the
former, does it make sense to automatically push these operators for all types?
Should any type that has an `add` method automatically get `+` as a synonym as
well? That may not be desirable.

I'm very supportive of the floating-point protocol proposal in general, but I
feel the arithmetic and comparison operations should be exposed by operators
alone and not by methods, where there is a suitable operator that has the
intended meaning.

--
Dave

That seems like a purely syntactic concern that could potentially be
addressed in other ways, though. I'm not sure the choice of "duplicate all
operators using verbosely-named methods" is the best one for the reasons I
mentioned above, and the question of "how do we cleanly unify operators
with other protocol requirements?" seems out-of-scope and orthogonal to
this proposal.

Given that we already have existing cases in the language where operators
are declared within protocols (`Equatable` being the first one that comes
to mind), I would recommend that this proposal follow that pattern for
consistency and then the community continue a separate discussion about
operators in protocols, which may or may not lead to changes across the
entire language and standard library. The protocol operators discussion
feels like a much larger topic that deserves to be discussed in its own
right without bogging down the rest of this proposal.

···

On Tue, Apr 26, 2016 at 8:18 AM David Sweeris <davesweeris@mac.com> wrote:

I’m with Nicola on this one. Operators are currently odd in that they have
to be declared globally. Everything else about protocol conformance is kept
within the conforming type.

- Dave Sweeris

On Apr 26, 2016, at 9:28 AM, Tony Allevato via swift-evolution < > swift-evolution@swift.org> wrote:

On Sun, Apr 24, 2016 at 2:57 AM Nicola Salmoria via swift-evolution < > swift-evolution@swift.org> wrote:

> > func isEqual(to other: Self) ->Bool
> > func isLess(than other: Self) ->Bool
> > func isLessThanOrEqual(to other: Self) ->Bool
>
> I'm still not sure why these are methods instead of operators.

I think this is an *excellent* choice, and I hope it is the first step to
completely removing operators from protocols.

IMHO throwing operators into protocols is inconsistent and confusing.
Having regular methods and a single generic version of the operator that
calls down on the type’s methods is clearer and guarantees that generic
code can avoid ambiguities by calling the methods directly, instead of
having to rely only on heavily overloaded global operators.

I personally disagree on this point. To me, a protocol describes a set of
requirements for a type to fulfill, which includes things other than
methods. Just as a protocol can define initializers, properties, and
associated types that a type must define in order to conform, it makes
sense that a protocol would also define which operators a conforming type
must support.

Introducing a mapping between names and operators poses a few problems:

– IMO, they are overly verbose and add noise to the definition. This makes
the language look less clean (I'm getting visions of NSDecimalNumber).
– They expose two ways to accomplish the same thing (writing
`x.isEqual(to: y)` and `x == y`).
– Do certain operators automatically get mapped to method names with
appropriate signatures across all types, or does a conforming type still
have to provide that mapping by implementing the operators separately? If
it's the latter, that's extra work for the author of the type writing the
protocol. If it's the former, does it make sense to automatically push
these operators for all types? Should any type that has an `add` method
automatically get `+` as a synonym as well? That may not be desirable.

I'm very supportive of the floating-point protocol proposal in general,
but I feel the arithmetic and comparison operations should be exposed by
operators alone and not by methods, where there is a suitable operator that
has the intended meaning.


Nicola

_______________________________________________
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

> > func isEqual(to other: Self) ->Bool
> > func isLess(than other: Self) ->Bool
> > func isLessThanOrEqual(to other: Self) ->Bool
>
> I'm still not sure why these are methods instead of operators.

I think this is an *excellent* choice, and I hope it is the first step to
completely removing operators from protocols.

IMHO throwing operators into protocols is inconsistent and confusing.
Having regular methods and a single generic version of the operator that
calls down on the type’s methods is clearer and guarantees that generic
code can avoid ambiguities by calling the methods directly, instead of
having to rely only on heavily overloaded global operators.

I personally disagree on this point. To me, a protocol describes a set of
requirements for a type to fulfill, which includes things other than
methods. Just as a protocol can define initializers, properties, and
associated types that a type must define in order to conform, it makes
sense that a protocol would also define which operators a conforming type
must support.

Well, I'm not sure about that. A protocol describes what a type can do, so
it's debatable whether a global function is within this scope.

Operators are magically special: you can declare them inside a protocol and
require them to be available for conformance, even if they don't belong to
the type. You can't do the same thing for normal global functions, yet
conceptually global functions and operators are the same thing.

Introducing a mapping between names and operators poses a few problems:

– IMO, they are overly verbose and add noise to the definition. This makes
the language look less clean (I'm getting visions of NSDecimalNumber).
– They expose two ways to accomplish the same thing (writing
`x.isEqual(to: y)` and `x == y`).
– Do certain operators automatically get mapped to method names with
appropriate signatures across all types, or does a conforming type still
have to provide that mapping by implementing the operators separately? If
it's the latter, that's extra work for the author of the type writing the
protocol. If it's the former, does it make sense to automatically push
these operators for all types? Should any type that has an `add` method
automatically get `+` as a synonym as well? That may not be desirable.

The difference at the protocol declaration is between:

protocol Equatable {
    func ==(lhs: Self, rhs: Self) -> Bool
}

and:

protocol Equatable {
    func isEqual(to other: Self) -> Bool
}

func ==<T: Equatable>(lhs: T, rhs: T) -> Bool {
    return lhs.isEqual(to: rhs)
}

so the latter is a bit more verbose, but arguably clearer in intent, and
not different from how you would define any generic global function using a
protocol, or from how you can define protocol extensions with default
implementations that take advantage of the protocol's core methods.

The difference for the conformance is between:

extension Foo : Equatable { }

func ==(lhs: Foo, rhs: Foo) -> Bool {
    return <comparison>
}

and:

extension Bar : Equatable {
    func isEqual(to: Bar) -> Bool {
        return <comparison>
    }
}

the former way to define the conformance can be confusing to newbies. The
latter is straightforward and consistent with the usual way to adopt a
protocol.

The == operator looks exactly the same at its use points, but the way how
it's implemented is different.
In the former case, it's many overloads of a global function, which can
stress the compiler's type inference and doesn't offer an obvious way to
disambiguate in case of ambiguities.
In the latter case, there is only one generic definition of ==, which
automatically applies to all types that conform to the protocol.

Nicola

···

On Tue, Apr 26, 2016 at 4:28 PM, Tony Allevato <allevato@google.com> wrote:

On Sun, Apr 24, 2016 at 2:57 AM Nicola Salmoria via swift-evolution < > swift-evolution@swift.org> wrote:

I'm very supportive of the floating-point protocol proposal in general,
but I feel the arithmetic and comparison operations should be exposed by
operators alone and not by methods, where there is a suitable operator that
has the intended meaning.


Nicola

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

Comments inline.

Hi Xiaodi —

Thanks for the detailed comments. Some thoughts inline.

* What is your evaluation of the proposal?

+1 in intent. Specifics require further refinement. For example:

Internal inconsistencies in capitalization:
* `signalingNaN` but `isSignalingNan` and `isNan`

This is a typo. Should be signalingNan.

Parameter labels, or whatever they're called now, do not reflect newly
adopted Swift syntax in SE-0046:
* `static func maximum(x: Self, _ y: Self) -> Self` should be `static
func maximum(_ x: Self, _ y: Self) -> Self`, etc.

Infelicitous use of prepositions to conform superficially to new
naming guidelines:
* `isEqual(to:)` is fine, but for consistency there's
`isLessThanOrEqual(to:)`, which is not fine, because the preposition
"to" applies only to "equal" and not to "less than”

Since `adding(_:)` is instead now an operator in the current version
of the proposal, could comparison functions also be operators only?

They could, but you still need isUnordered(with: ) and isTotallyOrdered(with: ), as they don’t have operator equivalents.

My two cents are that the comparison functions that have operators
should stick with those operators only instead of the names.
Stylistically, I think `isLessThanOrEqual(to:)` is less than
cromulent.

Some thoughts on `isUnordered(with:)` and `isTotallyOrdered(with:)`--

1. As you mention below, implementations are free to expose
IEEE754-mandated operations with names that differ from the
specification. Looking to Java and C# as examples of how other C-style
languages implement floating point types, C-style languages have taken
a free hand in how they deal with comparison with NaN.

So long as we're not going down the road of having methods that expose
distinct unordered-quiet and unordered-signaling comparison predicates
as defined in IEEE754 (do any C-style languages do that?), there's
probably some freedom to determine whether `isUnordered(with:)` is
necessary as a standalone method or whether it's already sufficiently
provided for by other methods. In the end, `x.isUnordered(with: y)` is
really equivalent to `x.isNan || y.isNan`, which can be immediately
grokked by any reader of code.

2. `isTotallyOrdered(with:)` isn't quite apt as a name. As I
understand it, totalOrder(x, y) "reflects" a total ordering such that,
when x is ordered "below" y, the return value is true. (Both quoted
words reflect the terminology used in the specification.) So perhaps
`isTotallyOrdered(below:)` or even just `isOrdered(below:)` would be
more apt. Certainly `with` is not the correct label for the parameter,
as any two values that don't compare unordered should be "totally
ordered with" each other. This is especially problematic if we're
going to keep `isUnordered(with:)`, since those two methods would
suggest a complementary logical relationship that just isn't there.

Yes, if we drop isUnordered(with:), then renaming this isOrdered(below:) would work. I’m OK with this approach.

Incorrect nomenclature in an attempt to correct previously misleading
nomenclature:
* `leastMagnitude` should be `leastPositive` or `minPositive`, because
magnitudes can be zero: it's bonkers that
`Double.minimumMagnitude(0.0, Double.leastMagnitude) <
Double.leastMagnitude`!
(Likewise, `leastNormalMagnitude` should be `leastNormalPositive` or
`minPositive`, and `greatestFiniteMagnitude` should be
`greatestFinite` or `maxFinite`)

Inconsistencies with Integer protocols (at least, as they are currently):
* properties named "least..." or "greatest..." are inconsistent with
conceptually similar properties such as `Int.min` and `Int.max`

`min` and `max` were deliberately avoided based on a discussion with the Apple standard library team; these properties don’t really behave like the integer bounds properties, so naming them similarly may be confusing.

I agree absolutely that `min` and `max`, standalone, are misleading
and shouldn't be used. That said, I would argue that `maxFinite` is
demonstrably the correct name because it's the most succinct one
that's both accurate and consistent with other usage. Adding
"magnitude" clarifies nothing here and in fact made me do a
double-take on first reading: I had to think for a split second
whether there exists a greater floating point value that's a finite
non-magnitude: it's of course absurd, but you have to think about it
the first time. Meanwhile, `greatestFinite` means exactly the same
thing as `maxFinite`, but now you're introducing a different word
where it's not necessary to clarify the meaning or differentiate from
standalone `max`.

My biggest concern with `maxFinite` is that it would seem to also require a `minFinite`, which would then be easily confused with e.g. DBL_MIN (which means something radically different), and also implies that they might not be symmetric after all. To my ear, “magnitude” more readily suggests the existing symmetry: namely that I can get the finite bounds as +/-.greatestFiniteMagnitude.

That's a fair point.

FWIW, 754 actually explicitly describes least[Positive]Magnitude as “the positive number of least magnitude” (the definition of nextUp, 5.3.1).

Your point about magnitudes being non-zero is reasonable, but I think you’ve taken it a step to far; it could be corrected by simply changing `leastMagnitude` to either `leastPositiveMagnitude` or `leastNonzeroMagnitude`.

By the same reasoning here, `minPositive` (instead of
`leastMagnitude`) is equally accurate, more consistent with usage
elsewhere, and probably more accessible to non-native English
speakers. Throughout the IEEE specification, "magnitude" is used in
relation to absolute values, which is not really in play here.

In any case, we agree that `leastMagnitude` must at minimum be renamed
to exclude zero.

`leastNormalMagnitude` and `greatestFiniteMagnitude` are accurate as is. (`minPositive`, on the other hand, would be exceedingly misleading). Can you expand on why you want to change them? It seems like you simply prefer “positive” to “magnitude”?

I made a typo here (I blame jetlag): `minPositiveNormal` was what I
meant to type. Again, my rationale is that it is the least deviation
from `min` that accurately describes what's going on using the
simplest words. Here, the distinction between normal and subnormal is
unavoidable, but we don't need to talk about magnitudes, and there
really is no difference between "min" and "least”.

One is a word, the other is an abbreviation. The swift guidelines, for better or worse, counsel us to “avoid abbreviations”. The fact that “min" is a term of art makes it plausible, but I don’t think we’re desperate to save two characters in the name.

Also potentially useful (actually, definitely useful in implementing
floating point strides) would be the properties `maxExactInteger`
(and, I suppose, a corresponding `minExactInteger`).

An early draft of the protocol had these. Naming this property is *hard*, because every floating-point value larger than `maxExactInteger` is … an exact integer. If you want to be unambiguously precise, you end up with something horrible. Ultimately I punted on this issue, but I would definitely support adding it in the future if an appropriate name can be found, or if a compelling use case arises (I don’t think it’s actually needed for implementing strides).

Ouch, yes, this isn't trivial to name. The best I have is
`greatestUnitDecrementableInteger`, which is awful.

Use of one term-of-art (`ulp`), as Swift naming guidelines allow and
even encourage, but failure to use more widely understood terms of
art:
* `squareRoot()` should be `sqrt()`
* something really ought to be done about
`truncatingRemainder(dividingBy:)`--the fact that the comments tell
the reader that `truncatingRemainder(dividingBy:)` is equivalent to C
`fmod` suggests to me that `fmod` may be a widely understood
term-of-art
I argue strongly that Swift's naming guidelines about terms-of-art
should be understood to encourage terms used widely in other languages
for basic mathematical functions instead of written-out English
equivalents--e.g. `asinh` instead of`inverseHyperbolicSine`. Looking
to other C-style languages, all seem to accept these shorter terms
as-is without writing them out.

sqrt( ) I could support, but fmod( ) is an absolutely terrible name for a relatively rarely-used function. If there were a good term-of-art, I would want to use it, but AFAIK there isn’t.

I should note that the free functions sqrt( ) and fmod( ) won’t go away with this proposal. They will continue to be supplied by the math overlay for Float, Double, CGFloat, just not for the FloatingPoint protocol. So why do we need them in the FloatingPoint protocol at all?

The squareRoot( ) and remainder( ) methods are distinct from most of the other <math.h> functions in that IEEE 754 considers them to be "basic operations” as defined by clause 5 of the standard (IEEE 754 spells out the name “squareRoot” FWIW, though there’s no requirement that we follow that).

Even IEEE754 is interestingly inconsistent here. Throughout, it's
written as "squareRoot", but when it comes to "recommended" functions,
the reciprocal of the square root is written "rSqrt" (see table 9.1).
I'd highly recommend setting a good example of when things are
terms-of-art by going with "sqrt”.

Clause 9, being non-normative, didn't receive nearly as much editorial attention.

While we're on the topic of IEEE754 section 5 functions, we're missing
abs(x) in the protocol.

abs() would have come from SignedArithmetic, but you’re right that it’s now missing and should be added to FloatingPoint.

And speaking of absolute value functions, IEEE754 calls it "minNumMag"
and "maxNumMag" when comparing two values but, when it comes to
summing, recommends a function called "sumAbs". A missed opportunity
for consistency, IMO. When implementing in Swift, then, was there a
rationale for having `minimumMagnitude` instead of the much shorter
but equally grokkable `minAbs` (abs being, IMO, a term-of-art like
sqrt)?

maxAbs is unclear (to me); I could easily imagine that it returns max(abs(x), abs(y)). Given that these methods have no precedent in C-family languages, there isn’t an actual term of art to follow, and the meaning of maxAbs isn’t particularly obvious.

Actually, now that you point it out, `max(abs(x), abs(y))` is exactly
what I thought maxNumMag would do, which it does not. I'm not sure
`minimumMagnitude()` helps in this respect. I kind of want to suggest
a more descriptive name, something like `minimumComparingMagnitude()`
or `minimumIgnoringSign()`.

And then speaking of implementations of IEEE minNum and maxNum--are
those static methods necessary in the protocol? What is gained over
having just the comparison operators?

While one can write these operations in terms of the comparison operators, it’s a bit unwieldy to do so, and requires some heroics from the optimizer to turn such an expanded definition into e.g. the arm64 FMAXNM instruction or the AVX-512 VRANGE instruction. It’s somewhat easier to wire up those optimizations this way.

Similarly, having these methods directly available is a useful optimization hook for user-defined types that conform to the protocol (though the protocol *will* provide default implementations, so you’re not obligated to do so).

Because of this it makes sense to require them as methods in the FloatingPoint protocol, rather than only as free functions. [truncatingRemainder( ) is not required by IEEE 754, but it doesn’t impose a significant implementation burden and eases the transition for folks currently using operator %.]

I'm not sure I buy the "ease" argument. Surely, if folks are using
operator %, it's equivalently difficult to transition either to the
free function or to `truncatingRemainder()`? Suppose you deprecate %
and recommend fmod(), then just leave `truncatingRemainder()` out of
the protocol. After all, the rationale for deprecation is that people
aren't using it correctly...

fmod() wraps the C stdlib fmod(), so only exists for types supported by the C stdlib. The truncatingRemainder() method is available for all FloatingPoint types (these two sets of types are equivalent for Swift today, but if someone wants to write a library type that conforms to FloatingPoint, it would provide truncatingRemainder).

I suppose the salient question here is, given that fmod-like behavior
is usually not an ideal choice for floating point types, do we want to
make it available (or, if no default implementation will be provided,
force it to be made available) for those floating point types outside
the C stdlib?

···

On Wed, Apr 20, 2016 at 11:37 AM, Stephen Canon <scanon@apple.com> wrote:

On Apr 20, 2016, at 12:04 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:
On Wed, Apr 20, 2016 at 5:38 AM, Stephen Canon <scanon@apple.com> wrote:

On Apr 19, 2016, at 6:34 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

Comments inline.

Hi Xiaodi —

Thanks for the detailed comments. Some thoughts inline.

* What is your evaluation of the proposal?

+1 in intent. Specifics require further refinement. For example:

Internal inconsistencies in capitalization:
* `signalingNaN` but `isSignalingNan` and `isNan`

This is a typo. Should be signalingNan.

Parameter labels, or whatever they're called now, do not reflect newly
adopted Swift syntax in SE-0046:
* `static func maximum(x: Self, _ y: Self) -> Self` should be `static
func maximum(_ x: Self, _ y: Self) -> Self`, etc.

Infelicitous use of prepositions to conform superficially to new
naming guidelines:
* `isEqual(to:)` is fine, but for consistency there's
`isLessThanOrEqual(to:)`, which is not fine, because the preposition
"to" applies only to "equal" and not to "less than”

Since `adding(_:)` is instead now an operator in the current version
of the proposal, could comparison functions also be operators only?

They could, but you still need isUnordered(with: ) and isTotallyOrdered(with: ), as they don’t have operator equivalents.

My two cents are that the comparison functions that have operators
should stick with those operators only instead of the names.
Stylistically, I think `isLessThanOrEqual(to:)` is less than
cromulent.

Some thoughts on `isUnordered(with:)` and `isTotallyOrdered(with:)`--

1. As you mention below, implementations are free to expose
IEEE754-mandated operations with names that differ from the
specification. Looking to Java and C# as examples of how other C-style
languages implement floating point types, C-style languages have taken
a free hand in how they deal with comparison with NaN.

So long as we're not going down the road of having methods that expose
distinct unordered-quiet and unordered-signaling comparison predicates
as defined in IEEE754 (do any C-style languages do that?), there's
probably some freedom to determine whether `isUnordered(with:)` is
necessary as a standalone method or whether it's already sufficiently
provided for by other methods. In the end, `x.isUnordered(with: y)` is
really equivalent to `x.isNan || y.isNan`, which can be immediately
grokked by any reader of code.

2. `isTotallyOrdered(with:)` isn't quite apt as a name. As I
understand it, totalOrder(x, y) "reflects" a total ordering such that,
when x is ordered "below" y, the return value is true. (Both quoted
words reflect the terminology used in the specification.) So perhaps
`isTotallyOrdered(below:)` or even just `isOrdered(below:)` would be
more apt. Certainly `with` is not the correct label for the parameter,
as any two values that don't compare unordered should be "totally
ordered with" each other. This is especially problematic if we're
going to keep `isUnordered(with:)`, since those two methods would
suggest a complementary logical relationship that just isn't there.

Yes, if we drop isUnordered(with:), then renaming this isOrdered(below:) would work. I’m OK with this approach.

Incorrect nomenclature in an attempt to correct previously misleading
nomenclature:
* `leastMagnitude` should be `leastPositive` or `minPositive`, because
magnitudes can be zero: it's bonkers that
`Double.minimumMagnitude(0.0, Double.leastMagnitude) <
Double.leastMagnitude`!
(Likewise, `leastNormalMagnitude` should be `leastNormalPositive` or
`minPositive`, and `greatestFiniteMagnitude` should be
`greatestFinite` or `maxFinite`)

Inconsistencies with Integer protocols (at least, as they are currently):
* properties named "least..." or "greatest..." are inconsistent with
conceptually similar properties such as `Int.min` and `Int.max`

`min` and `max` were deliberately avoided based on a discussion
with the Apple standard library team; these properties don’t really
behave like the integer bounds properties, so naming them similarly
may be confusing.

I agree absolutely that `min` and `max`, standalone, are misleading
and shouldn't be used. That said, I would argue that `maxFinite` is
demonstrably the correct name because it's the most succinct one
that's both accurate and consistent with other usage. Adding
"magnitude" clarifies nothing here and in fact made me do a
double-take on first reading: I had to think for a split second
whether there exists a greater floating point value that's a finite
non-magnitude: it's of course absurd, but you have to think about it
the first time. Meanwhile, `greatestFinite` means exactly the same
thing as `maxFinite`, but now you're introducing a different word
where it's not necessary to clarify the meaning or differentiate from
standalone `max`.

My biggest concern with `maxFinite` is that it would seem to also
require a `minFinite`, which would then be easily confused with
e.g. DBL_MIN (which means something radically different), and also
implies that they might not be symmetric after all. To my ear,
“magnitude” more readily suggests the existing symmetry: namely that I
can get the finite bounds as +/-.greatestFiniteMagnitude.

FWIW, 754 actually explicitly describes least[Positive]Magnitude as
“the positive number of least magnitude” (the definition of nextUp,
5.3.1).

Your point about magnitudes being non-zero is reasonable, but I
think you’ve taken it a step to far; it could be corrected by
simply changing `leastMagnitude` to either `leastPositiveMagnitude`
or `leastNonzeroMagnitude`.

By the same reasoning here, `minPositive` (instead of
`leastMagnitude`) is equally accurate, more consistent with usage
elsewhere, and probably more accessible to non-native English
speakers. Throughout the IEEE specification, "magnitude" is used in
relation to absolute values, which is not really in play here.

In any case, we agree that `leastMagnitude` must at minimum be renamed
to exclude zero.

`leastNormalMagnitude` and `greatestFiniteMagnitude` are accurate
as is. (`minPositive`, on the other hand, would be exceedingly
misleading). Can you expand on why you want to change them? It
seems like you simply prefer “positive” to “magnitude”?

I made a typo here (I blame jetlag): `minPositiveNormal` was what I
meant to type. Again, my rationale is that it is the least deviation
from `min` that accurately describes what's going on using the
simplest words. Here, the distinction between normal and subnormal is
unavoidable, but we don't need to talk about magnitudes, and there
really is no difference between "min" and "least”.

One is a word, the other is an abbreviation. The swift guidelines,
for better or worse, counsel us to “avoid abbreviations”. The fact
that “min" is a term of art makes it plausible, but I don’t think
we’re desperate to save two characters in the name.

Also potentially useful (actually, definitely useful in implementing
floating point strides) would be the properties `maxExactInteger`
(and, I suppose, a corresponding `minExactInteger`).

An early draft of the protocol had these. Naming this property is
*hard*, because every floating-point value larger than
`maxExactInteger` is … an exact integer. If you want to be
unambiguously precise, you end up with something horrible. Ultimately
I punted on this issue, but I would definitely support adding it in
the future if an appropriate name can be found, or if a compelling use
case arises (I don’t think it’s actually needed for implementing
strides).

Isn't this really maxResultOfAdding1?

···

on Wed Apr 20 2016, Stephen Canon <swift-evolution@swift.org> wrote:

On Apr 20, 2016, at 12:04 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:
On Wed, Apr 20, 2016 at 5:38 AM, Stephen Canon <scanon@apple.com> wrote:

On Apr 19, 2016, at 6:34 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

Use of one term-of-art (`ulp`), as Swift naming guidelines allow and
even encourage, but failure to use more widely understood terms of
art:
* `squareRoot()` should be `sqrt()`
* something really ought to be done about
`truncatingRemainder(dividingBy:)`--the fact that the comments tell
the reader that `truncatingRemainder(dividingBy:)` is equivalent to C
`fmod` suggests to me that `fmod` may be a widely understood
term-of-art
I argue strongly that Swift's naming guidelines about terms-of-art
should be understood to encourage terms used widely in other languages
for basic mathematical functions instead of written-out English
equivalents--e.g. `asinh` instead of`inverseHyperbolicSine`. Looking
to other C-style languages, all seem to accept these shorter terms
as-is without writing them out.

sqrt( ) I could support, but fmod( ) is an absolutely terrible name
for a relatively rarely-used function. If there were a good
term-of-art, I would want to use it, but AFAIK there isn’t.

I should note that the free functions sqrt( ) and fmod( ) won’t go
away with this proposal. They will continue to be supplied by the
math overlay for Float, Double, CGFloat, just not for the
FloatingPoint protocol. So why do we need them in the
FloatingPoint protocol at all?

The squareRoot( ) and remainder( ) methods are distinct from most
of the other <math.h> functions in that IEEE 754 considers them to
be "basic operations” as defined by clause 5 of the standard (IEEE
754 spells out the name “squareRoot” FWIW, though there’s no
requirement that we follow that).

Even IEEE754 is interestingly inconsistent here. Throughout, it's
written as "squareRoot", but when it comes to "recommended" functions,
the reciprocal of the square root is written "rSqrt" (see table 9.1).
I'd highly recommend setting a good example of when things are
terms-of-art by going with "sqrt”.

Clause 9, being non-normative, didn't receive nearly as much editorial attention.

While we're on the topic of IEEE754 section 5 functions, we're missing
abs(x) in the protocol.

abs() would have come from SignedArithmetic, but you’re right that it’s now missing and should be added to FloatingPoint.

And speaking of absolute value functions, IEEE754 calls it "minNumMag"
and "maxNumMag" when comparing two values but, when it comes to
summing, recommends a function called "sumAbs". A missed opportunity
for consistency, IMO. When implementing in Swift, then, was there a
rationale for having `minimumMagnitude` instead of the much shorter
but equally grokkable `minAbs` (abs being, IMO, a term-of-art like
sqrt)?

maxAbs is unclear (to me); I could easily imagine that it returns
max(abs(x), abs(y)). Given that these methods have no precedent in
C-family languages, there isn’t an actual term of art to follow, and
the meaning of maxAbs isn’t particularly obvious.

And then speaking of implementations of IEEE minNum and maxNum--are
those static methods necessary in the protocol? What is gained over
having just the comparison operators?

While one can write these operations in terms of the comparison
operators, it’s a bit unwieldy to do so, and requires some heroics
from the optimizer to turn such an expanded definition into e.g. the
arm64 FMAXNM instruction or the AVX-512 VRANGE instruction. It’s
somewhat easier to wire up those optimizations this way.

Similarly, having these methods directly available is a useful
optimization hook for user-defined types that conform to the protocol
(though the protocol *will* provide default implementations, so you’re
not obligated to do so).

Because of this it makes sense to require them as methods in the
FloatingPoint protocol, rather than only as free functions.
[truncatingRemainder( ) is not required by IEEE 754, but it doesn’t
impose a significant implementation burden and eases the transition
for folks currently using operator %.]

I'm not sure I buy the "ease" argument. Surely, if folks are using
operator %, it's equivalently difficult to transition either to the
free function or to `truncatingRemainder()`? Suppose you deprecate %
and recommend fmod(), then just leave `truncatingRemainder()` out of
the protocol. After all, the rationale for deprecation is that people
aren't using it correctly...

fmod() wraps the C stdlib fmod(), so only exists for types supported
by the C stdlib. The truncatingRemainder() method is available for
all FloatingPoint types (these two sets of types are equivalent for
Swift today, but if someone wants to write a library type that
conforms to FloatingPoint, it would provide truncatingRemainder).

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

--
Dave

Likewise, FWIW, +0 is ordered above -0 but is equal to it, so less than or
equal to is not the same thing as being ordered at or below.

···

On Wed, Apr 20, 2016 at 16:05 Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Less than or equal to is not the same as not greater than, because two
unordered values are not less than or equal to and also not greater than.
On Wed, Apr 20, 2016 at 15:51 plx via swift-evolution < > swift-evolution@swift.org> wrote:

FWIW, you can avoid the multiple-preposition issue it differently:

- isLessThanOrEqual(to:) => isNotGreaterThan(_:), isAtOrBelow(_:), etc.

…neither of which I can claim to really like, but such possibilities
exist.

Offering in case there's a better phrasing along similar lines.

On Apr 20, 2016, at 3:08 PM, Xiaodi Wu via swift-evolution < >> swift-evolution@swift.org> wrote:

I'm saying something else. The preposition that goes with "less" is
"than," while "to" goes only with "equal." By making "to" a parameter label
you've got {less than or equal} to, which is inelegant because the label
cannot be distributed to both parts--i.e. one cannot say "less than to or
equal to."

Put another way, I could just as well rewrite the method as
`equalToOrLess(than:)`. Now, the parameter is labeled "than" instead of
"to," yet the parameter serves the same purpose. Thus, I argue that the
proposed method name may meet the letter of the Swift guidelines but is
awkward.

On Wed, Apr 20, 2016 at 14:44 Dave Abrahams via swift-evolution < >> swift-evolution@swift.org> wrote:

on Tue Apr 19 2016, Xiaodi Wu <swift-evolution@swift.org> wrote:

> * What is your evaluation of the proposal?
>
> +1 in intent. Specifics require further refinement. For example:
>
> Internal inconsistencies in capitalization:
> * `signalingNaN` but `isSignalingNan` and `isNan`
>
> Parameter labels, or whatever they're called now, do not reflect newly
> adopted Swift syntax in SE-0046:
> * `static func maximum(x: Self, _ y: Self) -> Self` should be `static
> func maximum(_ x: Self, _ y: Self) -> Self`, etc.
>
> Infelicitous use of prepositions to conform superficially to new
> naming guidelines:
> * `isEqual(to:)` is fine, but for consistency there's
> `isLessThanOrEqual(to:)`, which is not fine, because the preposition
> "to" applies only to "equal" and not to "less than"

That seems like a huge stretch to me. Are you claiming it's wrong to
say “x is less than or equal to y,” or are you saying something else?

--
Dave

_______________________________________________
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

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

Naive question: is it necessary to make a trade-off here? Why not an
associated type Exponent that's Int for Float, Double, and Float80,
allowing for something else for bignums?

It’s an added (fairly minor) complexity to the API surface that confers
approximately zero benefit.

Alternatively, could `exponent` could be of type Self, just as `significand` is?

IEEE 754 allows this (“If logBFormat is a floating-point format, then the following operations are homogeneous), but IMO this makes the property more awkward to use in practice.

I wonder about that. Not sure what this property would be most
commonly used for, but if it's for subsequent FP calculations, being
of type Self here seems like it would be less rather than more
awkward.

Obviously, my motivation for suggesting this is that it gets around
the arbitrary (but admittedly generous) limit of Int exponents by
requiring only that the number of bits for the exponent is less than
or equal to the number of bits used for the significand, which IMO is
more defensible.

···

On Fri, Apr 22, 2016 at 10:31 AM, Stephen Canon <scanon@apple.com> wrote:

On Apr 22, 2016, at 11:26 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:
On Fri, Apr 22, 2016 at 9:56 AM, Stephen Canon <scanon@apple.com> wrote:

On Apr 22, 2016, at 10:54 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

– Steve

This runs into exactly the same issues; in the (extremely rare) cases where such enormous exponents are used, they tend to be coupled with surprisingly modest significands, so I don’t think this fixes anything.

There was some discussion of such formats on the IEEE 754 list circa 2007 w.r.t. edge cases of some of the usual library functions that break down when enormous exponent ranges are paired with small significands, but (much like this discussion) it was almost entirely theoretical. IIRC only one list member had actually ever had occasion to use such a format.

– Steve

···

On Apr 22, 2016, at 11:58 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Fri, Apr 22, 2016 at 10:31 AM, Stephen Canon <scanon@apple.com> wrote:

On Apr 22, 2016, at 11:26 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Fri, Apr 22, 2016 at 9:56 AM, Stephen Canon <scanon@apple.com> wrote:

On Apr 22, 2016, at 10:54 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Naive question: is it necessary to make a trade-off here? Why not an
associated type Exponent that's Int for Float, Double, and Float80,
allowing for something else for bignums?

It’s an added (fairly minor) complexity to the API surface that confers
approximately zero benefit.

Alternatively, could `exponent` could be of type Self, just as `significand` is?

IEEE 754 allows this (“If logBFormat is a floating-point format, then the following operations are homogeneous), but IMO this makes the property more awkward to use in practice.

I wonder about that. Not sure what this property would be most
commonly used for, but if it's for subsequent FP calculations, being
of type Self here seems like it would be less rather than more
awkward.

Obviously, my motivation for suggesting this is that it gets around
the arbitrary (but admittedly generous) limit of Int exponents by
requiring only that the number of bits for the exponent is less than
or equal to the number of bits used for the significand, which IMO is
more defensible.

First, something absent: I'm a little bit concerned by the act that
there's no means to convert between different concrete FloatingPoint
types. Something like the IntMax mechanism in Swift 2's IntegerType
might be useful, though there may be good reasons not to do that.

There are concrete conversions between all built in float types.
Those aren’t going away. Are you saying that you want init(_ value:
Float) etc to be protocol requirements?

IIUC, what's being suggested is

init<T: FloatingPoint>(_ x: T)

inits between different radices, while a feature we’d probably like to have eventually, are out of scope for this proposal.

The proposal does have:

  init<Source: BinaryFloatingPoint>(_ value: Source)

You're right—I missed that. My mistake. Consider that objection withdrawn.

···

--
Brent Royal-Gordon
Architechies

I tend to agree. I’d like the inconsistency concerning operators and dispatch to be resolved by investigating making operators into members, not by forcing operators to be second-class citizens. (Obviously this deserves its own proposal and its own discussion.)

Jordan

···

On Apr 26, 2016, at 08:47, Tony Allevato via swift-evolution <swift-evolution@swift.org> wrote:

That seems like a purely syntactic concern that could potentially be addressed in other ways, though. I'm not sure the choice of "duplicate all operators using verbosely-named methods" is the best one for the reasons I mentioned above, and the question of "how do we cleanly unify operators with other protocol requirements?" seems out-of-scope and orthogonal to this proposal.

Given that we already have existing cases in the language where operators are declared within protocols (`Equatable` being the first one that comes to mind), I would recommend that this proposal follow that pattern for consistency and then the community continue a separate discussion about operators in protocols, which may or may not lead to changes across the entire language and standard library. The protocol operators discussion feels like a much larger topic that deserves to be discussed in its own right without bogging down the rest of this proposal.

On Tue, Apr 26, 2016 at 8:18 AM David Sweeris <davesweeris@mac.com <mailto:davesweeris@mac.com>> wrote:
I’m with Nicola on this one. Operators are currently odd in that they have to be declared globally. Everything else about protocol conformance is kept within the conforming type.

- Dave Sweeris

On Apr 26, 2016, at 9:28 AM, Tony Allevato via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Sun, Apr 24, 2016 at 2:57 AM Nicola Salmoria via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
> > func isEqual(to other: Self) ->Bool
> > func isLess(than other: Self) ->Bool
> > func isLessThanOrEqual(to other: Self) ->Bool
>
> I'm still not sure why these are methods instead of operators.

I think this is an *excellent* choice, and I hope it is the first step to completely removing operators from protocols.

IMHO throwing operators into protocols is inconsistent and confusing. Having regular methods and a single generic version of the operator that calls down on the type’s methods is clearer and guarantees that generic code can avoid ambiguities by calling the methods directly, instead of having to rely only on heavily overloaded global operators.

I personally disagree on this point. To me, a protocol describes a set of requirements for a type to fulfill, which includes things other than methods. Just as a protocol can define initializers, properties, and associated types that a type must define in order to conform, it makes sense that a protocol would also define which operators a conforming type must support.

Introducing a mapping between names and operators poses a few problems:

– IMO, they are overly verbose and add noise to the definition. This makes the language look less clean (I'm getting visions of NSDecimalNumber).
– They expose two ways to accomplish the same thing (writing `x.isEqual(to: y)` and `x == y`).
– Do certain operators automatically get mapped to method names with appropriate signatures across all types, or does a conforming type still have to provide that mapping by implementing the operators separately? If it's the latter, that's extra work for the author of the type writing the protocol. If it's the former, does it make sense to automatically push these operators for all types? Should any type that has an `add` method automatically get `+` as a synonym as well? That may not be desirable.

I'm very supportive of the floating-point protocol proposal in general, but I feel the arithmetic and comparison operations should be exposed by operators alone and not by methods, where there is a suitable operator that has the intended meaning.


Nicola

_______________________________________________
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

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

Though that has been the plan for a long time, I wonder if the opposite approach is better: make operators always be global, and never be allowed as protocol members. That would give us a very consistent design.

-Chris

···

On Apr 26, 2016, at 9:19 AM, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

I tend to agree. I’d like the inconsistency concerning operators and dispatch to be resolved by investigating making operators into members, not by forcing operators to be second-class citizens. (Obviously this deserves its own proposal and its own discussion.)

There is a strong motivation for this approach though: we want the type checker to be scalable. John recently wrote an epic piece about why having tons of overloads is a really bad idea:
https://lists.swift.org/pipermail/swift-dev/Week-of-Mon-20160404/001650.html

It is *much* better for type checker performance to have (e.g.):

func +<T : FloatingPoint>(lhs : T, rhs : T) -> T { return lhs.add(rhs) }
func +<T : Integer>(lhs : T, rhs : T) -> T { return lhs.add(rhs) }

Rather than overloads for 4 floating point types, and 8+ integer types. We really need to eliminate all the “expression too complex” classes of issues, and this is an important cause of them.

-Chris

···

On Apr 26, 2016, at 8:47 AM, Tony Allevato via swift-evolution <swift-evolution@swift.org> wrote:

That seems like a purely syntactic concern that could potentially be addressed in other ways, though. I'm not sure the choice of "duplicate all operators using verbosely-named methods" is the best one for the reasons I mentioned above, and the question of "how do we cleanly unify operators with other protocol requirements?" seems out-of-scope and orthogonal to this proposal.

Also, sorry for not being explicit about this. I’m not a type checker expert, but I believe that using operator requirements imposes the same load on the type checker as having large overload sets.

-Chris

···

On Apr 26, 2016, at 11:42 AM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Apr 26, 2016, at 8:47 AM, Tony Allevato via swift-evolution <swift-evolution@swift.org> wrote:

That seems like a purely syntactic concern that could potentially be addressed in other ways, though. I'm not sure the choice of "duplicate all operators using verbosely-named methods" is the best one for the reasons I mentioned above, and the question of "how do we cleanly unify operators with other protocol requirements?" seems out-of-scope and orthogonal to this proposal.

There is a strong motivation for this approach though: we want the type checker to be scalable. John recently wrote an epic piece about why having tons of overloads is a really bad idea:
[swift-dev] A type-checking performance case study

It is *much* better for type checker performance to have (e.g.):

func +<T : FloatingPoint>(lhs : T, rhs : T) -> T { return lhs.add(rhs) }
func +<T : Integer>(lhs : T, rhs : T) -> T { return lhs.add(rhs) }

Rather than overloads for 4 floating point types, and 8+ integer types. We really need to eliminate all the “expression too complex” classes of issues, and this is an important cause of them.

> > func isEqual(to other: Self) ->Bool
> > func isLess(than other: Self) ->Bool
> > func isLessThanOrEqual(to other: Self) ->Bool
>
> I'm still not sure why these are methods instead of operators.

I think this is an *excellent* choice, and I hope it is the first step
to completely removing operators from protocols.

IMHO throwing operators into protocols is inconsistent and confusing.
Having regular methods and a single generic version of the operator that
calls down on the type’s methods is clearer and guarantees that generic
code can avoid ambiguities by calling the methods directly, instead of
having to rely only on heavily overloaded global operators.

I personally disagree on this point. To me, a protocol describes a set of
requirements for a type to fulfill, which includes things other than
methods. Just as a protocol can define initializers, properties, and
associated types that a type must define in order to conform, it makes
sense that a protocol would also define which operators a conforming type
must support.

Well, I'm not sure about that. A protocol describes what a type can do, so
it's debatable whether a global function is within this scope.

Operators are magically special: you can declare them inside a protocol
and require them to be available for conformance, even if they don't belong
to the type. You can't do the same thing for normal global functions, yet
conceptually global functions and operators are the same thing.

On the contrary, to my mind that's an argument to find some way to express
global functions as requirements for protocol conformance.

···

On Tue, Apr 26, 2016 at 12:36 PM, Nicola Salmoria via swift-evolution < swift-evolution@swift.org> wrote:

On Tue, Apr 26, 2016 at 4:28 PM, Tony Allevato <allevato@google.com> > wrote:

On Sun, Apr 24, 2016 at 2:57 AM Nicola Salmoria via swift-evolution < >> swift-evolution@swift.org> wrote:

Introducing a mapping between names and operators poses a few problems:

– IMO, they are overly verbose and add noise to the definition. This
makes the language look less clean (I'm getting visions of NSDecimalNumber).
– They expose two ways to accomplish the same thing (writing
`x.isEqual(to: y)` and `x == y`).
– Do certain operators automatically get mapped to method names with
appropriate signatures across all types, or does a conforming type still
have to provide that mapping by implementing the operators separately? If
it's the latter, that's extra work for the author of the type writing the
protocol. If it's the former, does it make sense to automatically push
these operators for all types? Should any type that has an `add` method
automatically get `+` as a synonym as well? That may not be desirable.

The difference at the protocol declaration is between:

protocol Equatable {
    func ==(lhs: Self, rhs: Self) -> Bool
}

and:

protocol Equatable {
    func isEqual(to other: Self) -> Bool
}

func ==<T: Equatable>(lhs: T, rhs: T) -> Bool {
    return lhs.isEqual(to: rhs)
}

so the latter is a bit more verbose, but arguably clearer in intent, and
not different from how you would define any generic global function using a
protocol, or from how you can define protocol extensions with default
implementations that take advantage of the protocol's core methods.

The difference for the conformance is between:

extension Foo : Equatable { }

func ==(lhs: Foo, rhs: Foo) -> Bool {
    return <comparison>
}

and:

extension Bar : Equatable {
    func isEqual(to: Bar) -> Bool {
        return <comparison>
    }
}

the former way to define the conformance can be confusing to newbies. The
latter is straightforward and consistent with the usual way to adopt a
protocol.

The == operator looks exactly the same at its use points, but the way how
it's implemented is different.
In the former case, it's many overloads of a global function, which can
stress the compiler's type inference and doesn't offer an obvious way to
disambiguate in case of ambiguities.
In the latter case, there is only one generic definition of ==, which
automatically applies to all types that conform to the protocol.

Nicola

I'm very supportive of the floating-point protocol proposal in general,
but I feel the arithmetic and comparison operations should be exposed by
operators alone and not by methods, where there is a suitable operator that
has the intended meaning.


Nicola

_______________________________________________
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