[swift-evolution] [Idea] Generic associated types

Karl Wagner razielim at gmail.com
Sun Mar 12 03:23:17 CDT 2017


> 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 <mailto:razielim at gmail.com>> wrote:
> 
>> On 12 Mar 2017, at 08:50, Xiaodi Wu <xiaodi.wu at gmail.com <mailto:xiaodi.wu at gmail.com>> wrote:
>> 
>> On Sun, Mar 12, 2017 at 1:39 AM, Karl Wagner <razielim at gmail.com <mailto:razielim at gmail.com>> wrote:
>> 
>>> On 12 Mar 2017, at 08:21, Xiaodi Wu <xiaodi.wu at gmail.com <mailto: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)
    }
}

- Karl
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170312/358f6092/attachment.html>


More information about the swift-evolution mailing list