> 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

Reply via email to