[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