[swift-evolution] Variadic generics discussion

Brent Royal-Gordon brent at architechies.com
Tue May 31 23:22:42 CDT 2016


> I agree that this is a better design for Swift than the monstrosity I started out with.
> 
> 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:
> 
> // Every Tn in T... is Fooable and Barrable
> let x : (T...)
> reduce(x, reducer, startingValue)
> 
> func reducer<X : ???>(startingValue: U, eachMemberOfT: X) -> U { ... }
> 
> 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.

You know, I'm not a big fan of the `&` syntax people are pushing in the `Any<>` thread, but if you ignore the Ceylon people begging for union types, I think a sensible alternative meaning for `|` would be "all common supertypes of these types". Then you could write:

	// Bottom here is the bottom type: the subtype of all types.
	(Int, String, Bool).reduce(Bottom, combine: |)

And you'll end up with something like:

	Equatable & Hashable & _Reflectable

Which are the three protocols all of those types support. If they'd supported no common protocols, it would've ended up with `Any`. (Interestingly, this is arguably the same operation which should be used to determine the return value of tuple subscripting.)

Of course, nothing about having this feature *requires* that we use `&` and `|`; that was just the inspiration for it.

> Other questions (inherent to any proposal) would be:
> 
> - 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?

As far as I know, tuples support a subset of argument list features. (For instance, they don't support default values.) So if you splat a tuple into a parameter list, the worst case is that you won't get to use features like those.

One mismatch is that tuples barely care about their argument labels, while parameter lists select overloads on them. My temptation would be to require you to disambiguate with `(x:y:z)` syntax if you were splatting in an unclear way.

	let triple = (0, 10, 2)
	
	stride(triple...)		// Illegal due to ambiguity; write one of these:
	stride(from:to:by:)(triple...)
	stride(from:through:by:)(triple...)

One useful thing here is that the arity and types of a tuple are known at compile time, so we can figure out ahead of time how to interpret the call. That wouldn't be the case if you could splat arrays.

(Incidentally, we might want to offer a pseudo-failable initializer to create a tuple from a sequence:

	if let triple = (Int, Int, Int)(intArray) {
		return stride(from:to:by:)(triple...)
	}

That would indirectly allow you to splat the contents of an array into a parameter list.)

> - As you said, how do variadic generics work in the 0- and 1-member cases?

I don't think 0-tuples are a big deal, except in that there's no good starting value for certain reductions. Adding a bottom type (as I did in the `reduce` example above) would fix that.

1-tuples are a bigger problem, because if you have a parameter like `(Any...)`, you can't afford to interpret `((Int, String))` as `(Int, String)`. It might be possible to support 1-tuples only in contexts where you're using a variadic tuple, but I'm kind of skeptical of that idea.

Personally, I wish we'd just acknowledge `(SomeType)` (or maybe `(SomeType, )`) as something different from `SomeType` and stop fretting. Sure, it's a semi-useless degenerate case, but so are one-case `enum`s and `Optional<Void>`s, and we don't ban those. Banning `T -> U` syntax has already removed one of the sources of ambiguity here.

-- 
Brent Royal-Gordon
Architechies



More information about the swift-evolution mailing list