[swift-evolution] Variadic generics discussion

Austin Zheng austinzheng at gmail.com
Mon May 30 13:17:31 CDT 2016


(inline, again)

On Mon, May 30, 2016 at 5:44 AM, plx via swift-evolution <
swift-evolution at swift.org> wrote:

> 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”)
>

Yes, this makes sense. #allequal(T...), maybe?


>
> …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 ?
>

Yes, an explicit "same length" constraint makes sense. Otherwise, if a pack
is derived from another pack they are implied to have the same length.

I think the second constraint should be possible already? I'll double-check.


>
> …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.
>

I'm not sure if 'fold' as currently expressed is powerful enough to do
something like this. I suppose a variant of fold that took its parameter
'inout' would work, especially if your 'fold' type was a (isFinished,
desiredValue) tuple (or maybe even just "desiredValue?" as an optional).


>
> 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.
>

I think union types (the A | B | C anonymous type commonly requested) would
naturally complement this sort of functionality. You could have something
like #union(T...), which is a scalar value whose type is T1 | T2 | ... | Tn.

Like you said, a lot of things are possible with even the simple model,
when dynamic casting is taken into account. It's just more difficult to
read and possibly slower to execute.


>
>
>
> 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.
>

I'll keep on thinking about this.


>
>
>
> On May 28, 2016, at 3:03 PM, Austin Zheng via swift-evolution <
> swift-evolution at swift.org> wrote:
>
> Hello swift-evolution,
>
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> 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/5d73ccf8/attachment.html>


More information about the swift-evolution mailing list