[swift-evolution] Variadic generics discussion

plx plxswift at icloud.com
Mon May 30 07:44:41 CDT 2016


I’ll keep this quick since I see upthread you have already revised the proposal.

> On May 29, 2016, at 12:36 PM, Austin Zheng <austinzheng at gmail.com> wrote:
> 
>> …something like the above is definitely a "nice to have", but not having it will close off certain use cases.
> 
> Oh, this is interesting. So a way to specify an equality relationship between "adjacent" sets of associated types?

Right, that was what I had in mind, and I can think of uses for such neighbor-to-neighbor type relationships.

There may be uses for other relationships—both beyond equality and beyond neighbor-to-neighbor—but I can’t think of any offhand.

One other pack-level constraint came to mind:

- can you enforce (T…) is *exactly* (T,T,T,…) N times? (e.g. T0 == T1, etc., not just "conforms to same protocols” or “have identical associated types”)

…along with some “pack-to-pack” constraints:

- if you have e.g. 2+ packs, can you enforce (T…) and (U…) have the same arity?
- if you have e.g. 2+ packs, could you enforce e.g. T.Foo… == U.Bar ?

…as always nice-to-have, but throwing them out there for consideration.

> I'll write something up.
> 
> for index in 0..<#count(T…) {
>     if let v = tables[index][key] {
>         valueCache[key] = v
>     	return v
>     }
> }
> 
> I assume this is all compile time code generation (unroll the loop in the source #count(T...) times for each arity that T... is instantiated as, with a different 'tables' pack member value for each unrolled loop iteration). 

I’d assume so too, but thinking it through I think it needs some alternative way of being expressed.

If you look @ the “obvious” implementation for the above as written, it unrolls into something like this:

  if let v = tables[0][key] { … } // assume returns out of here on success
  if let v = tables[1][key] { … } // assume returns out of here on success
  //…etc...

…but b/c tuples aren’t directly subscriptable, those `tables[index][key]` expressions themselves would perhaps get expanded into the equivalent of, e.g.:
 
  private func __subscriptTables(index index: Int, key: K) -> V? {
    switch index {
       case 0: return tables.0[key]
       case 1: return tables.1[key]
       // etc...
       default: fatalError(“Invalid subscript into variadic…blah blah”)
    }
  }

…and so the original expansion would be more like:

  if let v = __subscriptTables(index:0, key: key) { … } // assume returns out of here on success
  if let v = __subscriptTables(index:1, key: key) { … } // assume returns out of here on success
  //…etc...

….which repeats the switch at each line. In theory the optimizer can know to inline `__subscriptTables`, notice `index` is known at compile-time, replace the switch with direct access, and arrive at the code you “really want”:

  if let v = tables.0[key] { … } // assume returns out of here on success
  if let v = tables.1[key] { … } // assume returns out of here on success

…but that’s obviously putting a lot of pressure on the compiler to convert the `for index in #count(tables) { … }` code into something equivalent-but-reasonable.

I’ll be sure to look @ at the proposed `fold` in this light.

> This is interesting. Might it be possible with to accomplish this with existentials (this is sort of a cross-reference to a different proposal from the generics manifest)? An existential type as described below would work for any pack where all the elements were constrained in the same way. Not sure if it could be made to work in the case where the types in the pack are related to each other (as proposed earlier).
> 
> struct ChainSequenceIterator<E, S… where S:Sequence, S.Iterator.Element == E> {
> 
> 	// A single variable that contains each iterator in turn; specific type doesn't matter as long as the element is E
> 	private var iterator : Any<Iterator where .Element == E>?
> 
> 	// ...
> } 

Actually yes, I hadn’t thought of that and you could make it work in this case (although perhaps with some indirection overhead? and it seems also with some additional state tracking to know which iterator you actually have).

Where I’m not as sure is for something like a `ChainCollectionIndex` (same `stuff from A, then stuff from B, etc” concept, but for A, B collections, and so on).

That’s more clearly a case where what you *want* ideally is something like this:

  struct ChainCollection2<A:Collection,B:Collection> {
    let a: A; let b: B 
  }

  struct ChainCollectionIndex2<A:Comparable,B:Comparable> {
    private var sourceIndex: Sum2<A,B> // e.g. (A | B)
  }

…since to implement the APIs you need A and A.Index and B and B.Index to "match up". It’s probably possible here to do something like this instead:

  struct ChainCollectionIndex2<A:Comparable,B:Comparable> {
    private var boxedIndex: Box<Any> // actually either Box<A> or Box<B>
    private var whichIndex: AOrB // (E.G. enum ilke IndexFromA | IndexFromB)
  }

…with a lot of casting and so on, and perhaps also with existentials (and/or casting and open-as), but it’d be *cleaner* with the sum, still.

Either way you’re right that e.g. existentials and similar + state flags can cover a lot of these uses.

> 
>> 
>> D: Make Parameter-Packs Named
>> 
>> I understand the appeal of the `…T`-style syntax, but I’d at least consider giving parameter-packs a more-concrete existence, e.g. something like this:
>> 
>>   // strawman declaration syntax:
>>   struct Foo<I,J,K#ParameterPack> {
>> 
>>     // `K` here will refer to the variadic parameter-pack itself;
>>     // cf the `typealias` below , but basically K ~ the tuple of its types
>> 
>>     typealias KValueTuple = #tuple(K) // == tuple of values of type K.0, K.1, etc.)
>>     typealias KTypeTyple - #typeTuple(K) // == tuple of types like K.0, K.1
>>     typealias FirstK = #first(K) 
>>     typealias LastK = #last(K) 
>>     static var kArity: Int { return #count(K) } 
>>     // and so on
>> 
>>   }
>> 
>>   // straw man point-of-use syntax, can be adjusted of course:
>>   let concreteFoo = Foo<I,J,K:(K0,K1,K2,…,Kn)>
>> 
>> …which complicates the grammar, but IMHO feels a lot nicer than having a lot of "implicit rules" about what `…` means and so on.
> 
> I think it makes sense to make pack usage explicit. I think the dots at site of declaration don't really cause trouble, though, and are a little nicer to read than T#ParameterPack.

I agree #ParameterPack is awful and that T… at the declaration site is not a big deal.

What I don’t like is not having a way to refer to the pack itself other than via its “placeholder type” with some dots.

It also seems nicer to express things like a pack fusion with names for packs, but again in fairness it’s not terrible:

  struct Foo<T…> {
  }

  func +++<T…,U…>(lhs: Foo<T…>, rhs: Foo<U…>) -> Foo<#fuse(T…,U…)> 

…so I’m not sure what I think on this.

> 
>> 
>>> On May 28, 2016, at 3:03 PM, Austin Zheng via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>> 
>>> Hello swift-evolution,
>>> 
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160530/2df25230/attachment.html>


More information about the swift-evolution mailing list