[swift-evolution] Variadic generics discussion

Austin Zheng austinzheng at gmail.com
Tue May 31 20:49:37 CDT 2016


I agree that this is a better design for Swift than the monstrosity I
started out with.

The "biggest" technical challenge I see is being able to type a reduction
sort of operator on a heterogenous tuple based on on whatever protocols and
constraints are common to its constituent members. For example:

// Every Tn in T... is Fooable and Barrable
let x : (T...)
reduce(x, reducer, startingValue)

func reducer<X : ???>(startingValue: U, eachMemberOfT: X) -> U { ... }

How do we bound ??? such that 'reducer' is useful while still being
statically type sound? Honestly, that's the most interesting question to
me. Generalized existentials might help with that.

Other questions (inherent to any proposal) would be:

- How do we resolve the impedance mismatch between tuples and function
argument lists? Is it even worth trying to resolve this mismatch, given
that argument lists are intentionally not intended to mirror tuples?

- As you said, how do variadic generics work in the 0- and 1-member cases?

I definitely agree that tuples are a inherently variadic, inherently
generic, sufficiently primitive data structure to map well onto the concept.

Austin




On Tue, May 31, 2016 at 6:29 PM, Brent Royal-Gordon via swift-evolution <
swift-evolution at swift.org> wrote:

> > There's definitely the possibility of going off the deep end with
> complexity like C++, but since we already have tuples as a primitive
> language feature, I think there's a straightforward language design that
> enables the most important use cases for variadics. If we have "tuple
> splatting" for argument forwarding, and some support for iterating tuples,
> like we briefly discussed in the context of (4 x Int) fixed-sized
> homogeneous tuples, that'd probably be enough.
>
> I read the original proposal, started writing up a response along these
> lines, and then scrolled up to see what others had said. I'm glad you agree!
>
> Here's my sketch.
>
> * * *
>
> In general, my preferred direction for this feature would tie variadics
> closely to tuples, and variadic types closely to tuple types. To wit:
>
> 1. We should dust off the old tuple indexing proposal (I actually forgot
> it was never reviewed) and get it through, along with tuple `count`, `map`,
> and probably a few other Collection-like operations which generate fixed
> signatures. (I'm going to use a method-style syntax, but a #function-style
> syntax would work too; we could even introduce a .#method-style syntax.)
>
>         let tuple: (Int, Int, Int, Int) = (2, 4, 6, 8)
>         print(tuple[tuple.0])                   // => 6
>         print(tuple.map { $0 / 2 })     // => (1, 2, 3, 4)
>
> 2. We should allow you to use analogous operators on the type of a tuple,
> too.
>
>         let value: (Int, String)[1] = "Hello, world!"
>         (Int, String).map { $0.Type }           // => (Int.Type,
> String.Type)
>
> 3. We should change our existing variadic parameters to create tuples, not
> Arrays.
>
>         func print(_ items: (Any...), separator: String = "", terminator:
> String = "\n") {
>                 items.0 // Yes, that's a thing you can do
>
> 4. We should add a splat operator which unpacks tuples into (possibly
> variadic) parameters.
>
>         print(tuple...)
>
> 5. We should allow you to define variadic type parameters.
>
>         // Note: If you had written `items: Items` instead of `items:
> Items...`, it would've taken a tuple
>         // of various LocalizedStringConvertible types.
>         func localizedPrint<Items: (LocalizedStringConvertible...)>(_
> items: Items..., separator: String = "", terminator: String = "\n") {
>                 print(items.map { $0.localizedDescription }, separator:
> separator, terminator: terminator)
>         }
>
> 6. We should extend splat to unpack tuple types into (possibly variadic)
> type parameters.
>
>         typealias Pair = (String, Int)
>         let table: Dictionary<Pair...> = [:]
>
> (7. Optional pet-peeve fixer, which I will not actually demonstrate:
> Require unconstrained type parameters to be spelled `T: Any`, not just `T`.
> This will remove an asymmetry, in that a variable-length tuple of any type
> has to be spelled `T: (Any...)`, but a single parameter is just spelled
> `T`.)
>
> What do we end up with here?
>
> Zip:
>
>         struct ZipSequence<Sequences: (Sequence...)>: Sequence {
>                 typealias Iterator: ZipIterator<Sequences.map {
> $0.Iterator }...>
>
>                 var sequences: Sequences
>
>                 func makeIterator() -> ZipIterator<Sequences...> {
>                         return ZipIterator.init(sequences.map {
> $0.makeIterator() }...)
>                 }
>
>                 init(_ sequences: Sequences...) {
>                         self.sequences = sequences
>                 }
>         }
>
>         struct ZipIterator<Iterators: (IteratorProtocol...)>:
> IteratorProtocol {
>                 typealias Element = Iterators.map { $0.Element }
>
>                 var iterators: Iterators
>
>                 init(_ iterators: Iterators...) {
>                         self.iterators = iterators
>                 }
>
>                 mutating func next() -> Element? {
>                         // Note: It may be too difficult to assign a type
> to this reduction;
>                         // something like the proposal's `#invert` may
> thus be necessary.
>                         // If it is added, I would hope that an analogous
> operation would
>                         // be added to `Sequence`s of `Optional`s.
>                         let nextValues = iterators.map { $0.next()
> }.reduce(Optional( () )) { earlier, this in
>                                 guard let earlier = earlier, this = this
> else {
>                                         return nil
>                                 }
>                                 return earlier + (this)
>                         }
>                         guard let nextValues = nextValues else {
>                                 return nil
>                         }
>                         return nextValues
>                 }
>         }
>
> Function application is basically just a use of the splat feature:
>
>         // Note that the `args: Args` here is *not* `...`ed, so it takes a
> tuple.
>         // Meanwhile, the `Args` in `(Args...) -> Result` does have `...`,
> so it's looking
>         // for arguments which that tuple could be splatted into.
>         func apply<Args: (Any...), Return>(_ args: Args, to function:
> (Args...) -> Return) -> Return {
>                 return function(args...)
>         }
>
>         func method<Instance, Args: (Args...), Return>(_ name: String, on
> instance: Instance) -> ((Args...) -> Return)? {
>                 ...
>         }
>
> MultiMap:
>
>         func multiMap<Sequences: (Sequence...), Return>(_ sequences:
> (Sequences...), transform: (Sequences.map { $0.Iterator.Element }...) ->
> Return) -> [Return] {
>                 var returns: [Return] = []
>
>                 // You could do this by manually making iterators, but why
> bother when we already have the right abstraction?
>                 for elems in zip(sequences...) {
>                         returns.append(transform(elems...))
>                 }
>
>                 return returns
>         }
>
> Advantages of this approach, as I see them:
>
> * Variadics are entirely a creature of parameter lists and type parameter
> lists; outside of those, they are always represented as a tuple. That
> means, for instance, that there is no need for variadics to reach into
> places like variables.
>
> * `...` goes in exactly two places: After a type name within the
> definition of a tuple type, and after an expression being splatted into a
> parameter list. There's no proliferation of dots throughout the language or
> confusing leading-vs.-trailing dots thing.
>
> * Many of these features are useful in non-variadic code. For instance,
> Collection-like tuples are a feature people want already.
>
> * We stick closer to already-implemented features, rather than inventing a
> whole lot of completely novel stuff, like `#rest` and `#fold` and `#vector`.
>
> Disadvantages:
>
> * This leans rather heavily on tuple and tuple type features which will
> probably, at least for now, have to be built into the compiler. (You might
> be able to make them macros later.)
>
> * This _may_ raise the grim specter of 1-tuples, but I'm not sure about
> that. (Perhaps even the concrete-tuple members should be hashed, like
> `#count` and `#map`? That would allow them to be applied to single values.)
>
>
> --
> Brent Royal-Gordon
> Architechies
>
> _______________________________________________
> 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/20160531/26cd92d9/attachment.html>


More information about the swift-evolution mailing list