[swift-evolution] Fwd: [Idea] Generic associated types

Daniel Leping daniel at crossroadlabs.xyz
Sun Mar 12 08:09:09 CDT 2017


I'm totally +1 with this pitch and was going to draft it myself. Though,
since it's here let's pull it off.

Let's bring the same Functor (event considering it's not a _real_ functor)
example from higher-kinded types:

protocol Functor {
  associatedtype A
  func fmap<FB where FB ~= Self>(f: A -> FB.A) -> FB
}


And make it a functor:

protocol Functor {
  associatedtype A

  associatedtype F<B> : Functor where Functor.A == B


  func map<B>(f: (A) -> B) -> F<B>
}

Now we can implement it in a concrete class the way we want:

protocol Array {
  typealias A = Element

  typealis F<B> = Array<B> //Our where clause fits ok: Functor.A == B;

//Keep in mind we are free to use another class instead of Array and
it still will work.

//This is the difference with higher-kinded.

//I'm not saying higher-kinded is bad, I'm saying I would love to have both :)

  func map<B>(f: (A) -> B) -> F<B> {

  //implementation details

  }
}

The benefit here is that we can use the protocol, use its map in other
places. I.e.

protocol Monad : Functor {


  func flatMap<M : Monad>(f: (A) -> M) -> M {

     //pseudo code

     return flatten(map(f: f))

  }
}

and further... extend Monad, create Applicative, etc.

Does it make sense now?

On Sun, Mar 12, 2017 at 10:39 AM, Xiaodi Wu via swift-evolution <
swift-evolution at swift.org> wrote:

> 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.
>
>
> _______________________________________________
> 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/20170312/ca8ce126/attachment.html>


More information about the swift-evolution mailing list