<div dir="ltr">I agree that this is a better design for Swift than the monstrosity I started out with.<div><br></div><div>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:</div><div><br></div><div>// Every Tn in T... is Fooable and Barrable</div><div>let x : (T...)</div><div>reduce(x, reducer, startingValue)</div><div><br></div><div>func reducer<X : ???>(startingValue: U, eachMemberOfT: X) -> U { ... }</div><div><br></div><div>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.</div><div><br></div><div>Other questions (inherent to any proposal) would be:</div><div><br></div><div>- 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?</div><div><br></div><div>- As you said, how do variadic generics work in the 0- and 1-member cases?</div><div><br></div><div>I definitely agree that tuples are a inherently variadic, inherently generic, sufficiently primitive data structure to map well onto the concept.</div><div><br></div><div>Austin</div><div><br></div><div><br></div><div><br></div></div><div class="gmail_extra"><br><div class="gmail_quote">On Tue, May 31, 2016 at 6:29 PM, Brent Royal-Gordon via swift-evolution <span dir="ltr"><<a href="mailto:swift-evolution@swift.org" target="_blank">swift-evolution@swift.org</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><span class="">> 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.<br>
<br>
</span>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!<br>
<br>
Here's my sketch.<br>
<br>
* * *<br>
<br>
In general, my preferred direction for this feature would tie variadics closely to tuples, and variadic types closely to tuple types. To wit:<br>
<br>
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.)<br>
<br>
let tuple: (Int, Int, Int, Int) = (2, 4, 6, 8)<br>
print(tuple[tuple.0]) // => 6<br>
print(tuple.map { $0 / 2 }) // => (1, 2, 3, 4)<br>
<br>
2. We should allow you to use analogous operators on the type of a tuple, too.<br>
<br>
let value: (Int, String)[1] = "Hello, world!"<br>
(Int, String).map { $0.Type } // => (Int.Type, String.Type)<br>
<br>
3. We should change our existing variadic parameters to create tuples, not Arrays.<br>
<br>
func print(_ items: (Any...), separator: String = "", terminator: String = "\n") {<br>
items.0 // Yes, that's a thing you can do<br>
<br>
4. We should add a splat operator which unpacks tuples into (possibly variadic) parameters.<br>
<br>
print(tuple...)<br>
<br>
5. We should allow you to define variadic type parameters.<br>
<br>
// Note: If you had written `items: Items` instead of `items: Items...`, it would've taken a tuple<br>
// of various LocalizedStringConvertible types.<br>
func localizedPrint<Items: (LocalizedStringConvertible...)>(_ items: Items..., separator: String = "", terminator: String = "\n") {<br>
print(items.map { $0.localizedDescription }, separator: separator, terminator: terminator)<br>
}<br>
<br>
6. We should extend splat to unpack tuple types into (possibly variadic) type parameters.<br>
<br>
typealias Pair = (String, Int)<br>
let table: Dictionary<Pair...> = [:]<br>
<br>
(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`.)<br>
<br>
What do we end up with here?<br>
<br>
Zip:<br>
<br>
struct ZipSequence<Sequences: (Sequence...)>: Sequence {<br>
typealias Iterator: ZipIterator<Sequences.map { $0.Iterator }...><br>
<br>
var sequences: Sequences<br>
<br>
func makeIterator() -> ZipIterator<Sequences...> {<br>
return ZipIterator.init(sequences.map { $0.makeIterator() }...)<br>
}<br>
<br>
init(_ sequences: Sequences...) {<br>
self.sequences = sequences<br>
}<br>
}<br>
<br>
struct ZipIterator<Iterators: (IteratorProtocol...)>: IteratorProtocol {<br>
typealias Element = Iterators.map { $0.Element }<br>
<br>
var iterators: Iterators<br>
<br>
init(_ iterators: Iterators...) {<br>
self.iterators = iterators<br>
}<br>
<br>
mutating func next() -> Element? {<br>
// Note: It may be too difficult to assign a type to this reduction;<br>
// something like the proposal's `#invert` may thus be necessary.<br>
// If it is added, I would hope that an analogous operation would<br>
// be added to `Sequence`s of `Optional`s.<br>
let nextValues = iterators.map { $0.next() }.reduce(Optional( () )) { earlier, this in<br>
guard let earlier = earlier, this = this else {<br>
return nil<br>
}<br>
return earlier + (this)<br>
}<br>
guard let nextValues = nextValues else {<br>
return nil<br>
}<br>
return nextValues<br>
}<br>
}<br>
<br>
Function application is basically just a use of the splat feature:<br>
<br>
// Note that the `args: Args` here is *not* `...`ed, so it takes a tuple.<br>
// Meanwhile, the `Args` in `(Args...) -> Result` does have `...`, so it's looking<br>
// for arguments which that tuple could be splatted into.<br>
func apply<Args: (Any...), Return>(_ args: Args, to function: (Args...) -> Return) -> Return {<br>
return function(args...)<br>
}<br>
<br>
func method<Instance, Args: (Args...), Return>(_ name: String, on instance: Instance) -> ((Args...) -> Return)? {<br>
...<br>
}<br>
<br>
MultiMap:<br>
<br>
func multiMap<Sequences: (Sequence...), Return>(_ sequences: (Sequences...), transform: (Sequences.map { $0.Iterator.Element }...) -> Return) -> [Return] {<br>
var returns: [Return] = []<br>
<br>
// You could do this by manually making iterators, but why bother when we already have the right abstraction?<br>
for elems in zip(sequences...) {<br>
returns.append(transform(elems...))<br>
}<br>
<br>
return returns<br>
}<br>
<br>
Advantages of this approach, as I see them:<br>
<br>
* 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.<br>
<br>
* `...` 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.<br>
<br>
* Many of these features are useful in non-variadic code. For instance, Collection-like tuples are a feature people want already.<br>
<br>
* We stick closer to already-implemented features, rather than inventing a whole lot of completely novel stuff, like `#rest` and `#fold` and `#vector`.<br>
<br>
Disadvantages:<br>
<br>
* 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.)<br>
<br>
* 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.)<br>
<span class="HOEnZb"><font color="#888888"><br>
<br>
--<br>
Brent Royal-Gordon<br>
Architechies<br>
</font></span><div class="HOEnZb"><div class="h5"><br>
_______________________________________________<br>
swift-evolution mailing list<br>
<a href="mailto:swift-evolution@swift.org">swift-evolution@swift.org</a><br>
<a href="https://lists.swift.org/mailman/listinfo/swift-evolution" rel="noreferrer" target="_blank">https://lists.swift.org/mailman/listinfo/swift-evolution</a><br>
</div></div></blockquote></div><br></div>