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/93eeb7d5eee8e1a7245387c660d 53b03#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... *## 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. 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. *... 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. *## 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@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@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution