[swift-evolution] [Idea] Generic associated types

Xiaodi Wu xiaodi.wu at gmail.com
Sun Mar 12 03:39:55 CDT 2017


On Sun, Mar 12, 2017 at 3:23 AM, Karl Wagner <razielim at gmail.com> wrote:

>
> On 12 Mar 2017, at 09:11, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>
> On Sun, Mar 12, 2017 at 3:02 AM, Karl Wagner <razielim at gmail.com> wrote:
>
>>
>> On 12 Mar 2017, at 08:50, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>>
>> On Sun, Mar 12, 2017 at 1:39 AM, Karl Wagner <razielim at gmail.com> wrote:
>>
>>>
>>> On 12 Mar 2017, at 08:21, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>>>
>>> Sorry, I'm confused. The following works:
>>>
>>> ```
>>> protocol Promise {
>>>   associatedtype Result
>>> }
>>>
>>> protocol Scanner {
>>>   associatedtype ScannerPromise : Promise
>>>   func frobnicate<T>(_: T) -> ScannerPromise
>>>     where ScannerPromise.Result == T
>>> }
>>> ```
>>>
>>> Why does it matter if `ScannerPromise` is generic or not?
>>>
>>>
>>> That’s some pretty strange syntax. I admit I didn’t even try that.
>>> ScannerPromise would necessarily have to be generic in this context,
>>> because I want to bind one of its associated types to a generic parameter
>>> from a function. There is no way a non-generic type could do that.
>>>
>>> That said, even though the compiler accepts your code, it doesn’t seem
>>> to actually work:
>>>
>>> protocol Promise {
>>>   associatedtype Result
>>>   func await() -> Result
>>> }
>>>
>>> protocol Scanner {
>>>   associatedtype ScannerPromise : Promise
>>>   func frobnicate<T>(_: T) -> ScannerPromise
>>>     where ScannerPromise.Result == T
>>> }
>>>
>>> func use<S: Scanner, T>(_ s: S, _ t: T) -> T {
>>> return s.frobnicate(t).await()
>>> }
>>>
>>>
>>>
>>> 3.0.2: Segfault
>>>
>>> 3.1:
>>>
>>> error: repl.swift:13:14: error: cannot invoke 'frobnicate' with an
>>> argument list of type '(T)'
>>>     return s.frobnicate(t).await()
>>>              ^
>>>
>>> repl.swift:13:14: note: expected an argument list of type '(T)'
>>>     return s.frobnicate(t).await()
>>>
>>
>> That's because your `T` in `use` has no relationship with your `T` in
>> `protocol Scanner`; you just happen to have chosen the same letter of the
>> alphabet. This becomes clear if you rename:
>>
>> ```
>> func use<S: Scanner, U>(_ s: S, _ t: U) -> U {
>>   return s.frobnicate(t).await()
>> }
>>
>> *// cannot invoke 'frobnicate' with an argument list of type '(U)'*
>> *// expected an argument list of type '(T)'*
>> ```
>>
>> However, this works:
>>
>> ```
>> func use<S: Scanner, T>(_ s: S, _ t: T) -> T
>>   where S.ScannerPromise.Result == T {
>>   return s.frobnicate(t).await()
>> }
>> ```
>>
>> ...or just this:
>>
>> ```
>> func use<S: Scanner>(
>>   _ s: S, _ t: S.ScannerPromise.Result
>> ) -> S.ScannerPromise.Result {
>>   return s.frobnicate(t).await()
>> }
>> ```
>>
>> - Karl
>>>
>>
>> No, what you’ve done is something different. “frobnicate” is itself a
>> generic function, so it should work if you rename the parameter to “U".
>> You’ve just punted the constraint down the abstraction hierarchy.
>>
>> This becomes clear if you try to continue working at the protocol-level:
>>
>> extension Scanner {
>> func synchronised_frob<T>(_ t: T) -> T {
>>     return frobnicate(t).await()
>> }
>> }
>>
>>
>> error: repl.swift:14:16: error: cannot invoke 'frobnicate' with an
>> argument list of type '(T)'
>>         return frobnicate(t).await()
>>                ^
>>
>> repl.swift:14:16: note: expected an argument list of type '(T)'
>>         return frobnicate(t).await()
>>
>>
> Hmm, I'm not sure what you're trying to accomplish here. If I have an
> instance `s` that conforms to `Scanner`, then it must have a concrete
> associated type `ScannerPromise.Result`. `s.frobnicate()` can only take an
> argument of type `ScannerPromise.Result`; you want to pass an argument of
> arbitrary type `T` and have an existing instance conforming to `Scanner`
> retroactively change the type of `ScannerPromise.Result`?
>
>
> Yes, but I may want ScannerPromise.Result to be a generic parameter. I
> refer you to my original email:
>
> Even with SE-0142, this kind of constraint would not be possible. What I
> would like to write is something like this:
>
> protocol Promise {
>     associatedtype Result
>     func await() -> Result
> }
>
> protocol Scanner {
>     associatedtype ScanPromise<X>: Promise where Result == X // (incl.
> SE-0142)
>
>     func promiseScan<T>(from: Offset, until: @escaping (Offset, Item) ->
> T?) -> ScanPromise<T?>
> }
>
> So, for example, I could write something like this (allowing my
> asynchronous Scanners to all automatically implement a synchronous API):
>
> extension Scanner {
>     func scan(from f: Offset, until u: (Offset, Item) -> T?) -> T? {
>         return promiseScan(from: f, until: u).await()
>     }
> }
>
> A conforming Promise would look like this:
>
> class MyPromise<Result>: Promise {
>     func await() -> Result { … }
> }
>
> And a conforming scanner would look like this:
>
> class Scanner {
>     typealias ScanPromise<X> = MyPromise<X> // compiler checks where
> clause: Result == X
>
>     public func promiseScan<T>(from f: Offset, until u: @escaping (Offset,
> Item) -> T?) -> MyPromise<T?> {
>        return MyPromise(from: f, until: u)
>     }
> }
>

I see. Makes sense now. I'm out of my depth here, but that does sound like
what you want here is higher kinded types.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170312/41f8a755/attachment.html>


More information about the swift-evolution mailing list