[swift-evolution] protocol-oriented integers (take 2)

Xiaodi Wu xiaodi.wu at gmail.com
Sat Jan 14 10:09:50 CST 2017

On Sat, Jan 14, 2017 at 1:32 AM, Jacob Bandes-Storch via swift-evolution <
swift-evolution at swift.org> wrote:

> Great work, all. I'm not sure I ever commented on SE-0104, so I've read
> through this one more carefully. Here are some things that came to mind:
> *## Arithmetic*
> Why are ExpressibleByIntegerLiteral and init?<T:BinaryInteger>(exactly:)
> required? I could understand needing access to 0, but this could be
> provided by a static property or nullary initializer. It doesn't seem like
> all types supporting arithmetic operations would necessarily be convertible
> from an arbitrary binary integer.
> I tried to evaluate the Arithmetic protocol by considering what it means
> for higher-dimensional types such as CGPoint and CGVector. My use case was
> a linear interpolation function:
>     func lerp<T: Arithmetic>(from a: T, to b: T, by ratio: T) -> T {
>         return a + (b - a) * ratio
>     }
> If I've read the proposal correctly, this definition works for integer and
> floating-point values. But we can't make it work properly for CGVector (or,
> perhaps less mathematically correct but more familiar, CGPoint). It's okay
> to define +(CGVector, CGVector) and -(CGVector, CGVector), but *(CGVector,
> CGVector) and /(CGVector, CGVector) don't make much sense. What we really
> want is *(CGVector, *CGFloat*) and /(CGVector, *CGFloat*).
> After consulting a mathematician, I believe what the lerp function really
> wants is for its generic param to be an affine space
> <https://en.wikipedia.org/wiki/Affine_space>. I explored this a bit here:
> https://gist.github.com/jtbandes/93eeb7d5eee8e1a7245387c660d53b
> 03#file-affine-swift-L16-L18
> This approach is less restrictive and more composable than the proposed
> Arithmetic protocol, which can be viewed as a special case with the
> following definitions:
>     extension Arithmetic: AffineSpace, VectorSpace {  // not actually
> allowed in Swift
>         typealias Displacement = Self
>         typealias Scalar = Self
>     }
> It'd be great to be able to define a lerp() which works for all
> floating-point and integer numbers, as well as points and vectors (assuming
> a glorious future where CGPoint and CGVector have built-in arithmetic
> operations). But maybe the complexity of these extra protocols isn't worth
> it for the stdlib...

I think, in the end, it's the _name_ that could use improvement here. As
the doc comments say, `Arithmetic` is supposed to provide a "suitable basis
for arithmetic on scalars"--perhaps `ScalarArithmetic` might be more
appropriate? It would make it clear that `CGVector` is not meant to be a
conforming type.

> *## BinaryInteger*
> I'm a little confused by the presence of init(extendingOrTruncating:) for
> *all* binary integers. Why does it make sense to be able to write
> UInt16(extendingOrTruncating: (-21 as Int8)) ? In my mind, sign-extension
> only has meaning when the source and destination types are both signed.

Here (just IMHO), I disagree. Since any binary integer can be truncated and
any can be right-shifted, it makes sense for `init(extendingOrTruncating:)`
to be available for all of them. If I understand the proposal correctly,
`Int(extendingOrTruncating: (-1 as Int8))` would give you a different
result than `Int(extendingOrTruncating: (255 as UInt8)`.

Although I would not be against a clamp() function in the standard library,
> "init(clamping:)" sounds strange to me. What about calling it
> "init(nearestTo:)"?  One could also define a FloatingPoint version of
> init(nearestTo:), i.e. lround(). For maximum annoyance and explicitness,
> you could even rename the existing init(_:) to init(truncating:) to make it
> clear that truncation, not regular rounding, occurs when converting from
> floating-point.

I would disagree with that as well; the existing `init(_:)` truncates the
fractional part but errors if that value is outside the representable
range, while the word "truncating" makes it sound like something is done to
make any possible source value give you a destination value (a la
"extendingOrTruncating" for integers).

Meanwhile, "nearest to" is problematic for me because either 127 and 129 is
"nearest to" 128, and neither integer is "nearest to" 500, yet
`Int8(clamping: 128)` and `Int8(clamping: 500)` both give you 127. This
operation is very different from lround() for floating point, which in
Swift is `rounded(.toNearestOrAwayFromZero)` (or just `rounded()`).

In both these cases, I think it's important to use spellings that
distinguish doing things to the fractional part of floating point values
and doing things to the binary representation of integer values. I think
there's great value in using the term "clamp", which is very different from
"nearest"; and in using an unlabeled `init(_:)` for initializing from FP
values, which is most similar to the unlabeled `init(_:)` for initializing
from integer values, as opposed to your suggested `init(truncating:)` which
connotes some similarity to `init(extendingOrTruncating:)` for integer

*... masking shifts*
> The example claims that "(30 as UInt8) &>> 11" produces 3, because it
> right-shifts 30 by 3. But isn't the bitWidth of UInt8 always 8 since it's a
> fixed-width type? Why should 11 get masked to 3 before the shift? (Also, it
> might be a good idea to choose examples with numbers whose base-ten
> representations don't look like valid binary. 😉) What use cases are there
> for masking shifts? I was under the impression that "smart shifts" were
> pretty much how the usual shift instructions already behaved.
> (Minor: were the smart shift operators supposed to be included as
> BinaryInteger protocol requirements? I only see them in the "heterogeneous
> shifts" extension.)
> *... init<T: BinaryInteger>(_ source: T)*
> Now a thought experiment: suppose you wanted to write an
> arbitrary-precision BigInt, or any binary integer such as Int256. The
> BinaryInteger protocol requires you to provide init<T:BinaryInteger>(_
> source: T). Given the source of type T, how do you access its bits? Is
> repeated use of word(at:) the recommended way? If so, it might be nice to
> include a "wordCount" returning the number of available words; otherwise I
> suppose the user has to use something like bitWidth/(8*MemoryLayout<Int>.size),
> which is pretty ugly.
> *## FixedWidthInteger*
> Why is popcount restricted to FixedWidthInteger? It seems like it could
> theoretically apply to any UnsignedInteger.

You can perfectly legitimately get a popcount for a signed integer. It's
just looking at the binary representation and counting the ones. But then
with two's complement, it'd have to be restricted to FixedWidthInteger and
not BinaryInteger, because the same negative value would have a different
popcount depending on the type's bitwidth. I'd disagree strongly with
removing popcount from signed binary integers. However, I suppose the same
requirement could be applied to both FixedWidthInteger and UnsignedInteger.

*## Heterogenous shifts, equality, and comparison*
> These look great. How will the default implementations be provided?
> (Equivalent question: how would a user define their own heterogeneous
> operators?) I suppose this works:
>     static func &>> <Other : BinaryInteger>(lhs: Self, rhs: Other) -> Self
> {
>         // delegate to the protocol requirement &>>(Self, Self)
>         return self &>> Self(extendingOrTruncating: rhs)
>     }
> But for operations you can't delegate so easily... I'm imagining trying to
> implement heterogeneous comparison (i.e. < ) and the best I can come up
> with uses signum() and word(at:)...
> Also, should these be protocol requirements so user-defined types can
> specialize them?
> *## Masking arithmetic*
> Do &* and &+ and &- need their operands to have the same type, or could
> these be heterogeneous too (at least &+ and &-)?
> Jacob
> On Fri, Jan 13, 2017 at 12:47 PM, Max Moiseev via swift-evolution <
> swift-evolution at swift.org> wrote:
>> Hi everyone,
>> Back in June 2016 we discussed the new design of the integer types for
>> the standard library. It even resulted in acceptance of SE-0104
>> <https://github.com/apple/swift-evolution/blob/master/proposals/0104-improved-integers.md> for
>> Swift 3. Unfortunately we were not able to implement it in time for the
>> release.
>> But it was not forgotten, although, as time went by, a few changes needed
>> to be made in order to reflect the current state of the language.
>> Without further introduction, please welcome the refined proposal to make
>> integers in Swift more suitable for generic programming.
>> Available in this gist https://gist.github.com/m
>> oiseev/62ffe3c91b66866fdebf6f3fcc7cad8c and also inlined below.
>> Max
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170114/260d2c13/attachment.html>

More information about the swift-evolution mailing list