[swift-evolution] [Completing Generics] Variadic generics

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


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.

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/be7607c4/attachment.html>


More information about the swift-evolution mailing list