[swift-evolution] [Completing Generics] Variadic generics

Jonathan Tang jonathan.d.tang at gmail.com
Thu Mar 10 02:51:38 CST 2016


On Thu, Mar 10, 2016 at 12:43 AM, Jonathan Tang <jonathan.d.tang at gmail.com>
wrote:

> On Wed, Mar 2, 2016 at 5:22 PM, Douglas Gregor via swift-evolution <
> swift-evolution at swift.org> wrote:
>
>>
>> *Variadic generics*
>>
>> Currently, a generic parameter list contains a fixed number of generic
>> parameters. If one has a type that could generalize to any number of
>> generic parameters, the only real way to deal with it today involves
>> creating a set of types. For example, consider the standard library’s “zip”
>> function. It returns one of these when provided with two arguments to zip
>> together:
>>
>> public struct Zip2Sequence<Sequence1 : Sequence,
>>                            Sequence2 : Sequence> : Sequence { … }
>>
>> public func zip<Sequence1 : Sequence, Sequence2 : Sequence>(
>>               sequence1: Sequence1, _ sequence2: Sequence2)
>>             -> Zip2Sequence<Sequence1, Sequence2> { … }
>>
>>
>> Supporting three arguments would require copy-paste of those of those:
>>
>> public struct Zip3Sequence<Sequence1 : Sequence,
>>                            Sequence2 : Sequence,
>>                            Sequence3 : Sequence> : Sequence { … }
>>
>> public func zip<Sequence1 : Sequence, Sequence2 : Sequence, Sequence3 :
>> Sequence>(
>>               sequence1: Sequence1, _ sequence2: Sequence2, _ sequence3:
>> sequence3)
>>             -> Zip3Sequence<Sequence1, Sequence2, Sequence3> { … }
>>
>>
>> Variadic generics would allow us to abstract over a set of generic
>> parameters. The syntax below is hopelessly influenced by C++11 variadic
>> templates <http://www.jot.fm/issues/issue_2008_02/article2/> (sorry),
>> where putting an ellipsis (“…”) to the left of a declaration makes it a
>> “parameter pack” containing zero or more parameters and putting an ellipsis
>> to the right of a type/expression/etc. expands the parameter packs within
>> that type/expression into separate arguments. The important part is that we
>> be able to meaningfully abstract over zero or more generic parameters, e.g.:
>>
>> public struct ZipIterator<... *Iterators* : IteratorProtocol> : Iterator
>> {  *// zero or more type parameters, each of which conforms to
>> IteratorProtocol*
>>   public typealias Element = (*Iterators.Element...*)
>>     *// a tuple containing the element types of each iterator in
>> Iterators*
>>
>>   var (*...iterators*): (*Iterators...*)    *// zero or more stored
>> properties, one for each type in Iterators*
>>   var reachedEnd: Bool = false
>>
>>
>>   public mutating func next() -> Element? {
>>
>>     if reachedEnd { return nil }
>>
>>
>>     guard let values = (*iterators.next()...*) {   *// call “next” on
>> each of the iterators, put the results into a tuple named “values"*
>>
>>       reachedEnd = true
>>
>>       return nil
>>
>>     }
>>
>>
>>     return values
>>
>>   }
>> }
>>
>> public struct ZipSequence<*...Sequences* : Sequence> : Sequence {
>>   public typealias Iterator = ZipIterator<*Sequences.Iterator...*>   *//
>> get the zip iterator with the iterator types of our Sequences*
>>
>>   var (...*sequences*): (*Sequences**...*)    *// zero or more stored
>> properties, one for each type in Sequences*
>>
>>   *// details ...*
>> }
>>
>> Such a design could also work for function parameters, so we can pack
>> together multiple function arguments with different types, e.g.,
>>
>> public func zip<*... Sequences : SequenceType*>(*... sequences:
>> Sequences...*)
>>             -> ZipSequence<*Sequences...*> {
>>   return ZipSequence(*sequences...*)
>> }
>>
>>
>> Finally, this could tie into the discussions about a tuple “splat”
>> operator. For example:
>>
>> func apply<... Args, Result>(fn: (Args...) -> Result,    *// function
>> taking some number of arguments and producing Result*
>>                            args: (Args...)) -> Result {  *// tuple of
>> arguments*
>>   return fn(*args...*)                                     // expand the
>> arguments in the tuple “args” into separate arguments
>> }
>>
>>
>>
> The application of this to function types is most interesting to me.
> Right now, when I need client code to register callbacks of potentially
> different arities, I make the function signature take an Args structure
> with a method to unpack it by position:
>
> struct Args {
>   func arg<T>(i: Int) -> T {
>     // Bounds-checking and dynamic typecasting/marshalling
>     guard let result = myValue as? T else {
>       throw TypeConversionError()
>     }
>     return result
>   }
> }
>
> func addHandler(name: String, handler: Args throws -> AnyObject) {
>   ....
> }
>
> func foo(x: String) { ... }
> func bar(y: Int, z: String) { ... }
>
> addHandler("foo", { args in try foo(args.arg(0)) })
> addHandler("bar", { args in try bar(args.arg(0), args.arg(1)) })
>
> If variadics could let the client just do
>
> addHandler("foo", foo)
> addHandler("bar", bar)
>
> and then preserve the type signature of foo & bar as they're stored in a
> dictionary, that'd be great for my library's users.  Repeated across the
> whole library ecosystem, it'd make it much easier for one library's
> functions to play nice with another library's callbacks, because you
> wouldn't need proprietary adapter classes that force a dependency on
> another library just so that client code can use the two together easily.
>
> But this only works if the type is not erased, i.e. if I can define a
> dictionary of type [String : (...ParameterType throws -> AnyObject)] and
> then call a function with apply(handlers["foo"], ["myParam"]).  This
> requires at least three features:
>
> 1. Variadic generics - and not just in function signatures, but also as
> part of the function *type*, and as structure members, and as associated
> Element types for Dictionary etc.
> 2. An array-splat operator which lets me apply these functions to a data
> structure, checking the types dynamically as it converts them, *or*
> 3. A tuple-splat operator to apply functions to a tuple, *plus* an
> array-to-tuple conversion.
>

4. Probably "Opening existentials" too, to make the array-to-tuple or
array-to-function conversion work.  The data in the array is heterogenous,
as are the variadic generic parameters.  It could work if you could loop
over variadic parameters in a type and attempt to unwrap the existential in
the array as that type, reporting an error if not.


> The actual data that gets passed to the functions is dynamic and subject
> to error-checking, so it doesn't help if it only works with tuples and not
> arrays etc.
>
> Not sure how likely the combination of these happening is, but I think
> it'd be a big win for Swift's library ecosystem.  It's hard to pass
> callbacks across third-party libraries otherwise, because the type
> signatures depend upon proprietary types that won't be in the receiving
> library.
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160310/ebe7787f/attachment.html>


More information about the swift-evolution mailing list