> 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
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution