[PROPOSAL]Return subclass type to a protocol where a superclass is defined without the need for associatedtype


(Yogev Sitton) #1

I have a class that is conforming to a protocol with a method that requires a specific return type.
In case I want to return a subclass of the return type I am forced to use an associatedtype that feels like a hack.

As an example:

protocol MyShapeProtocol {
    func make() -> Shape?
}

class Circle : Shape {}

class CircleMaker : MyShapeProtocol{
    func make() -> Circle? {
        return Circle()
    }
}

This will not work.
For that to work I’ll need to use toe associatedtype “hack”:

protocol MyShapeProtocol {
    associatedtype ShapeReturnType : Shape
    func make() -> ShapeReturnType?
}

class Circle : Shape {}

class CircleMaker : MyShapeProtocol{
    func make() -> Circle? {
        return Circle()
    }
}

Is there a real value in adding the associatedtype line?


(Howard Lovatt) #2

I think this should be part of completing generics.

···

On Sunday, 17 April 2016, Yogev Sitton via swift-evolution < swift-evolution@swift.org> wrote:

I have a class that is conforming to a protocol with a method that
requires a specific return type.
In case I want to return a subclass of the return type I am forced to use
an associatedtype that feels like a hack.

As an example:

*protocol MyShapeProtocol {*
* func make() -> Shape?*
*}*

*class Circle : Shape {}*

*class CircleMaker : MyShapeProtocol{*
* func make() -> Circle? {*
* return Circle()*
* }*
*}*

This will not work.
For that to work I’ll need to use toe associatedtype “hack”:

*protocol MyShapeProtocol {*
* associatedtype ShapeReturnType : Shape*
* func make() -> ShapeReturnType?*
*}*

*class Circle : Shape {}*

*class CircleMaker : MyShapeProtocol{*
* func make() -> Circle? {*
* return Circle()*
* }*
*}*

Is there a real value in adding the associatedtype line?

--
-- Howard.


#3

Hello Yogev,

I think your question belongs to the swift-users mailing list (quoting https://lists.swift.org/mailman/listinfo):

- swift-evolution: Discussion of the evolution of Swift, including new language features and new APIs.
- swift-users: For users to get help with or ask questions about Swift or its related tools

Gwendal Roué

···

Le 17 avr. 2016 à 10:52, Yogev Sitton via swift-evolution <swift-evolution@swift.org> a écrit :

I have a class that is conforming to a protocol with a method that requires a specific return type.
In case I want to return a subclass of the return type I am forced to use an associatedtype that feels like a hack.

As an example:

protocol MyShapeProtocol {
    func make() -> Shape?
}

class Circle : Shape {}

class CircleMaker : MyShapeProtocol{
    func make() -> Circle? {
        return Circle()
    }
}

This will not work.
For that to work I’ll need to use toe associatedtype “hack”:

protocol MyShapeProtocol {
    associatedtype ShapeReturnType : Shape
    func make() -> ShapeReturnType?
}

class Circle : Shape {}

class CircleMaker : MyShapeProtocol{
    func make() -> Circle? {
        return Circle()
    }
}

Is there a real value in adding the associatedtype line?
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Yogev Sitton) #4

In what way?

···

On Sun, Apr 17, 2016 at 3:53 PM -0700, "Howard Lovatt" <howard.lovatt@gmail.com> wrote:

I think this should be part of completing generics.

On Sunday, 17 April 2016, Yogev Sitton via swift-evolution <swift-evolution@swift.org> wrote:
I have a class that is conforming to a protocol with a method that requires a specific return type.In case I want to return a subclass of the return type I am forced to use an associatedtype that feels like a hack.
As an example:
protocol MyShapeProtocol { func make() -> Shape?}
class Circle : Shape {}
class CircleMaker : MyShapeProtocol{ func make() -> Circle? { return Circle() }}
This will not work.For that to work I’ll need to use toe associatedtype “hack”:
protocol MyShapeProtocol { associatedtype ShapeReturnType : Shape func make() -> ShapeReturnType?}
class Circle : Shape {}
class CircleMaker : MyShapeProtocol{ func make() -> Circle? { return Circle() }}
Is there a real value in adding the associatedtype line?

--
-- Howard.


(Ross O'Brien) #5

As of Swift 2.2, if a variable has a closure type of e.g. () -> Shape, a
closure of type () -> Circle would be considered a match. If a class
implements 'func make() -> Shape', a subclass implementing 'func make() ->
Circle' has to override. However, if a protocol requires a 'func make() ->
Shape', a type implementing 'func make() -> Circle' isn't considered to be
conforming. That does seem strange.

···

On Mon, Apr 18, 2016 at 12:55 AM, Yogev Sitton via swift-evolution < swift-evolution@swift.org> wrote:

In what way?

On Sun, Apr 17, 2016 at 3:53 PM -0700, "Howard Lovatt" < > howard.lovatt@gmail.com> wrote:

I think this should be part of completing generics.

On Sunday, 17 April 2016, Yogev Sitton via swift-evolution < >> swift-evolution@swift.org> wrote:

I have a class that is conforming to a protocol with a method that
requires a specific return type.
In case I want to return a subclass of the return type I am forced to
use an associatedtype that feels like a hack.

As an example:

*protocol MyShapeProtocol {*
* func make() -> Shape?*
*}*

*class Circle : Shape {}*

*class CircleMaker : MyShapeProtocol{*
* func make() -> Circle? {*
* return Circle()*
* }*
*}*

This will not work.
For that to work I’ll need to use toe associatedtype “hack”:

*protocol MyShapeProtocol {*
* associatedtype ShapeReturnType : Shape*
* func make() -> ShapeReturnType?*
*}*

*class Circle : Shape {}*

*class CircleMaker : MyShapeProtocol{*
* func make() -> Circle? {*
* return Circle()*
* }*
*}*

Is there a real value in adding the associatedtype line?

--
-- Howard.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Yogev Sitton) #6

Hi Gwendal,

I’m actually trying to start a discussion that will change the current behavior of the language.
That’s why I posted this on the swift-evolution mailing list and not swift-users.

Yogev

···

On Apr 18, 2016, at 12:48 PM, Gwendal Roué <gwendal.roue@gmail.com> wrote:

Hello Yogev,

I think your question belongs to the swift-users mailing list (quoting https://lists.swift.org/mailman/listinfo):

- swift-evolution: Discussion of the evolution of Swift, including new language features and new APIs.
- swift-users: For users to get help with or ask questions about Swift or its related tools

Gwendal Roué

Le 17 avr. 2016 à 10:52, Yogev Sitton via swift-evolution <swift-evolution@swift.org> a écrit :

I have a class that is conforming to a protocol with a method that requires a specific return type.
In case I want to return a subclass of the return type I am forced to use an associatedtype that feels like a hack.

As an example:

protocol MyShapeProtocol {
   func make() -> Shape?
}

class Circle : Shape {}

class CircleMaker : MyShapeProtocol{
   func make() -> Circle? {
       return Circle()
   }
}

This will not work.
For that to work I’ll need to use toe associatedtype “hack”:

protocol MyShapeProtocol {
   associatedtype ShapeReturnType : Shape
   func make() -> ShapeReturnType?
}

class Circle : Shape {}

class CircleMaker : MyShapeProtocol{
   func make() -> Circle? {
       return Circle()
   }
}

Is there a real value in adding the associatedtype line?
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


#7

OK Yogev. But what is the problem actually? If some of your code does not do what you want, is it because of the language, or is it because you still have to learn something about the language?

Associated types are not a "hack", they are a feature. Maybe you are not using it properly, you see?

If you would tell how those MyShapeProtocol objects are used, it may get clearer.

Gwendal

···

Le 18 avr. 2016 à 11:53, Yogev Sitton <yogev.sitton@gmail.com> a écrit :

Hi Gwendal,

I’m actually trying to start a discussion that will change the current behavior of the language.
That’s why I posted this on the swift-evolution mailing list and not swift-users.


(Yogev Sitton) #8

That’s exactly my point - thank you for the quick summary Ross.
Protocols behaves differently than closures and classes.

···

On Apr 18, 2016, at 12:40 PM, Ross O'Brien <narrativium+swift@gmail.com> wrote:

As of Swift 2.2, if a variable has a closure type of e.g. () -> Shape, a closure of type () -> Circle would be considered a match. If a class implements 'func make() -> Shape', a subclass implementing 'func make() -> Circle' has to override. However, if a protocol requires a 'func make() -> Shape', a type implementing 'func make() -> Circle' isn't considered to be conforming. That does seem strange.

On Mon, Apr 18, 2016 at 12:55 AM, Yogev Sitton via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
In what way?

On Sun, Apr 17, 2016 at 3:53 PM -0700, "Howard Lovatt" <howard.lovatt@gmail.com <mailto:howard.lovatt@gmail.com>> wrote:

I think this should be part of completing generics.

On Sunday, 17 April 2016, Yogev Sitton via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
I have a class that is conforming to a protocol with a method that requires a specific return type.
In case I want to return a subclass of the return type I am forced to use an associatedtype that feels like a hack.

As an example:

protocol MyShapeProtocol {
    func make() -> Shape?
}

class Circle : Shape {}

class CircleMaker : MyShapeProtocol{
    func make() -> Circle? {
        return Circle()
    }
}

This will not work.
For that to work I’ll need to use toe associatedtype “hack”:

protocol MyShapeProtocol {
    associatedtype ShapeReturnType : Shape
    func make() -> ShapeReturnType?
}

class Circle : Shape {}

class CircleMaker : MyShapeProtocol{
    func make() -> Circle? {
        return Circle()
    }
}

Is there a real value in adding the associatedtype line?

--
-- Howard.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Yogev Sitton) #9

I’m referring you to Ross O’Brien’s post:
As of Swift 2.2, if a variable has a closure type of e.g. () -> Shape, a closure of type () -> Circle would be considered a match. If a class implements 'func make() -> Shape', a subclass implementing 'func make() -> Circle' has to override. However, if a protocol requires a 'func make() -> Shape', a type implementing 'func make() -> Circle' isn't considered to be conforming. That does seem strange.

Protocols behaves differently than closures and classes and I think they should behave the same.

···

On Apr 18, 2016, at 1:00 PM, Gwendal Roué <gwendal.roue@gmail.com> wrote:

Le 18 avr. 2016 à 11:53, Yogev Sitton <yogev.sitton@gmail.com> a écrit :

Hi Gwendal,

I’m actually trying to start a discussion that will change the current behavior of the language.
That’s why I posted this on the swift-evolution mailing list and not swift-users.

OK Yogev. But what is the problem actually? If some of your code does not do what you want, is it because of the language, or is it because you still have to learn something about the language?

Associated types are not a "hack", they are a feature. Maybe you are not using it properly, you see?

If you would tell how those MyShapeProtocol objects are used, it may get clearer.

Gwendal


(Ross O'Brien) #10

You may have to explain that metaphor (or link to an explanation) - what is
'trampoline' data?

···

On Mon, Apr 18, 2016 at 11:11 AM, Gwendal Roué <swift-evolution@swift.org> wrote:

> Le 18 avr. 2016 à 12:01, Yogev Sitton <yogev.sitton@gmail.com> a écrit :
>
> I’m referring you to Ross O’Brien’s post:
> As of Swift 2.2, if a variable has a closure type of e.g. () -> Shape, a
closure of type () -> Circle would be considered a match. If a class
implements 'func make() -> Shape', a subclass implementing 'func make() ->
Circle' has to override. However, if a protocol requires a 'func make() ->
Shape', a type implementing 'func make() -> Circle' isn't considered to be
conforming. That does seem strange.
>
> Protocols behaves differently than closures and classes and I think they
should behave the same.

All right, I get it.

Shape, as a return type, is "trampoline" data that wraps any Shape value,
when Circle is just a Circle. That's why the two functions () -> Shape? and
() -> Circle? don't match today.

But maybe they will eventually, thanks to your request!

Gwendal

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Ross O'Brien) #11

Thanks Gwendal, that makes sense - for the case where Shape is a protocol.

Since the OP wasn't clear about it, I tried the original example with both
the cases where Shape was declared as a protocol (i.e. Circle conforms) and
where Shape was declared as a class (i.e. Circle inherits). The same
problem occurs when Shape is a concrete type. Does the same explanation
apply? i.e. the 'MyShapeProtocol' trampoline has the same problem you've
described?

Yogev: this is the first stage - discussion on the list. The formal next
stage, assuming that there's some list support and the idea isn't on the
list of 'commonly rejected ideas' (I've been burned by this) is to write a
proposal using the draft proposal document in the Swift Evolution github
repository, and make a pull request. In due course proposals will be
scheduled for review. If a pull request for an implementation of an
acceptable proposal exists, the review may happen that much faster.

···

On Mon, Apr 18, 2016 at 11:43 AM, Gwendal Roué <gwendal.roue@gmail.com> wrote:

Those "trampolines" are visible in debugger stack traces, or in
Instruments, as "protocol witness" function calls. You may have seen them
already.

You see, a variable of type SomeProtocol, or the result of a method that
returns a protocol, does not contain a value of any concrete type that
adopts the protocol.

Instead, it contains a description of what should happen when a protocol
method is called. It's an indirection. An indirection that happens at
runtime. The compiler says: "I have to store a value of type T in a
variable declared as protocol P. I'll actually store the fact that when the
function f() of the protocol is called, it will actually call the function
f() of the type T." And those redirections are visible as "protocol
witness" in our stack traces.

This allows a function that uses a Swift protocols to be used with types
that are not known yet by the compiler, such as the types defined by the
user that use a framework. Compile once, and run later, through the
indirection.

This also explains why it's not trivial to implement Yogev's request: a
value of type Circle (concrete type) has not the same memory layout than a
value of type Shape (a protocol trampoline). The same for functions that
involve those types. Some conversion has to happen, and this conversion
must happen at runtime, as we've seen above. Such support is not
implemented (yet ?).

I hope I was clear :slight_smile:
Gwendal

Le 18 avr. 2016 à 12:19, Ross O'Brien <narrativium+swift@gmail.com> a
écrit :

You may have to explain that metaphor (or link to an explanation) - what
is 'trampoline' data?

On Mon, Apr 18, 2016 at 11:11 AM, Gwendal Roué <swift-evolution@swift.org> > wrote:

> Le 18 avr. 2016 à 12:01, Yogev Sitton <yogev.sitton@gmail.com> a écrit
:
>
> I’m referring you to Ross O’Brien’s post:
> As of Swift 2.2, if a variable has a closure type of e.g. () -> Shape,
a closure of type () -> Circle would be considered a match. If a class
implements 'func make() -> Shape', a subclass implementing 'func make() ->
Circle' has to override. However, if a protocol requires a 'func make() ->
Shape', a type implementing 'func make() -> Circle' isn't considered to be
conforming. That does seem strange.
>
> Protocols behaves differently than closures and classes and I think
they should behave the same.

All right, I get it.

Shape, as a return type, is "trampoline" data that wraps any Shape value,
when Circle is just a Circle. That's why the two functions () -> Shape? and
() -> Circle? don't match today.

But maybe they will eventually, thanks to your request!

Gwendal

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


#12

All right, I get it.

Shape, as a return type, is "trampoline" data that wraps any Shape value, when Circle is just a Circle. That's why the two functions () -> Shape? and () -> Circle? don't match today.

But maybe they will eventually, thanks to your request!

Gwendal

···

Le 18 avr. 2016 à 12:01, Yogev Sitton <yogev.sitton@gmail.com> a écrit :

I’m referring you to Ross O’Brien’s post:
As of Swift 2.2, if a variable has a closure type of e.g. () -> Shape, a closure of type () -> Circle would be considered a match. If a class implements 'func make() -> Shape', a subclass implementing 'func make() -> Circle' has to override. However, if a protocol requires a 'func make() -> Shape', a type implementing 'func make() -> Circle' isn't considered to be conforming. That does seem strange.

Protocols behaves differently than closures and classes and I think they should behave the same.


#13

Those "trampolines" are visible in debugger stack traces, or in Instruments, as "protocol witness" function calls. You may have seen them already.

You see, a variable of type SomeProtocol, or the result of a method that returns a protocol, does not contain a value of any concrete type that adopts the protocol.

Instead, it contains a description of what should happen when a protocol method is called. It's an indirection. An indirection that happens at runtime. The compiler says: "I have to store a value of type T in a variable declared as protocol P. I'll actually store the fact that when the function f() of the protocol is called, it will actually call the function f() of the type T." And those redirections are visible as "protocol witness" in our stack traces.

This allows a function that uses a Swift protocols to be used with types that are not known yet by the compiler, such as the types defined by the user that use a framework. Compile once, and run later, through the indirection.

This also explains why it's not trivial to implement Yogev's request: a value of type Circle (concrete type) has not the same memory layout than a value of type Shape (a protocol trampoline). The same for functions that involve those types. Some conversion has to happen, and this conversion must happen at runtime, as we've seen above. Such support is not implemented (yet ?).

I hope I was clear :slight_smile:
Gwendal

···

Le 18 avr. 2016 à 12:19, Ross O'Brien <narrativium+swift@gmail.com> a écrit :

You may have to explain that metaphor (or link to an explanation) - what is 'trampoline' data?

On Mon, Apr 18, 2016 at 11:11 AM, Gwendal Roué <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

> Le 18 avr. 2016 à 12:01, Yogev Sitton <yogev.sitton@gmail.com <mailto:yogev.sitton@gmail.com>> a écrit :
>
> I’m referring you to Ross O’Brien’s post:
> As of Swift 2.2, if a variable has a closure type of e.g. () -> Shape, a closure of type () -> Circle would be considered a match. If a class implements 'func make() -> Shape', a subclass implementing 'func make() -> Circle' has to override. However, if a protocol requires a 'func make() -> Shape', a type implementing 'func make() -> Circle' isn't considered to be conforming. That does seem strange.
>
> Protocols behaves differently than closures and classes and I think they should behave the same.

All right, I get it.

Shape, as a return type, is "trampoline" data that wraps any Shape value, when Circle is just a Circle. That's why the two functions () -> Shape? and () -> Circle? don't match today.

But maybe they will eventually, thanks to your request!

Gwendal

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Yogev Sitton) #14

Discussion has stopped on this topic.
Should I submit a proposal?

···

On Apr 18, 2016, at 1:55 PM, Ross O'Brien <narrativium+swift@gmail.com> wrote:

Thanks Gwendal, that makes sense - for the case where Shape is a protocol.

Since the OP wasn't clear about it, I tried the original example with both the cases where Shape was declared as a protocol (i.e. Circle conforms) and where Shape was declared as a class (i.e. Circle inherits). The same problem occurs when Shape is a concrete type. Does the same explanation apply? i.e. the 'MyShapeProtocol' trampoline has the same problem you've described?

Yogev: this is the first stage - discussion on the list. The formal next stage, assuming that there's some list support and the idea isn't on the list of 'commonly rejected ideas' (I've been burned by this) is to write a proposal using the draft proposal document in the Swift Evolution github repository, and make a pull request. In due course proposals will be scheduled for review. If a pull request for an implementation of an acceptable proposal exists, the review may happen that much faster.

On Mon, Apr 18, 2016 at 11:43 AM, Gwendal Roué <gwendal.roue@gmail.com <mailto:gwendal.roue@gmail.com>> wrote:
Those "trampolines" are visible in debugger stack traces, or in Instruments, as "protocol witness" function calls. You may have seen them already.

You see, a variable of type SomeProtocol, or the result of a method that returns a protocol, does not contain a value of any concrete type that adopts the protocol.

Instead, it contains a description of what should happen when a protocol method is called. It's an indirection. An indirection that happens at runtime. The compiler says: "I have to store a value of type T in a variable declared as protocol P. I'll actually store the fact that when the function f() of the protocol is called, it will actually call the function f() of the type T." And those redirections are visible as "protocol witness" in our stack traces.

This allows a function that uses a Swift protocols to be used with types that are not known yet by the compiler, such as the types defined by the user that use a framework. Compile once, and run later, through the indirection.

This also explains why it's not trivial to implement Yogev's request: a value of type Circle (concrete type) has not the same memory layout than a value of type Shape (a protocol trampoline). The same for functions that involve those types. Some conversion has to happen, and this conversion must happen at runtime, as we've seen above. Such support is not implemented (yet ?).

I hope I was clear :slight_smile:
Gwendal

Le 18 avr. 2016 à 12:19, Ross O'Brien <narrativium+swift@gmail.com <mailto:narrativium+swift@gmail.com>> a écrit :

You may have to explain that metaphor (or link to an explanation) - what is 'trampoline' data?

On Mon, Apr 18, 2016 at 11:11 AM, Gwendal Roué <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

> Le 18 avr. 2016 à 12:01, Yogev Sitton <yogev.sitton@gmail.com <mailto:yogev.sitton@gmail.com>> a écrit :
>
> I’m referring you to Ross O’Brien’s post:
> As of Swift 2.2, if a variable has a closure type of e.g. () -> Shape, a closure of type () -> Circle would be considered a match. If a class implements 'func make() -> Shape', a subclass implementing 'func make() -> Circle' has to override. However, if a protocol requires a 'func make() -> Shape', a type implementing 'func make() -> Circle' isn't considered to be conforming. That does seem strange.
>
> Protocols behaves differently than closures and classes and I think they should behave the same.

All right, I get it.

Shape, as a return type, is "trampoline" data that wraps any Shape value, when Circle is just a Circle. That's why the two functions () -> Shape? and () -> Circle? don't match today.

But maybe they will eventually, thanks to your request!

Gwendal

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Yogev Sitton) #15

I’m actually not sure what the next step is.
What is the official way to submit the proposal for the Swift team to consider?

···

> Le 18 avr. 2016 à 12:01, Yogev Sitton<yogev.sitton@gmail.com>a écrit :
>
> I’m referring you to Ross O’Brien’s post:
> As of Swift 2.2, if a variable has a closure type of e.g. () ->Shape, a closure of type () ->Circle would be considered a match. If a class implements 'func make() ->Shape', a subclass implementing 'func make() ->Circle' has to override. However, if a protocol requires a 'func make() ->Shape', a type implementing 'func make() ->Circle' isn't considered to be conforming. That does seem strange.
>
> Protocols behaves differently than closures and classes and I think they should behave the same.
All right, I get it.

Shape, as a return type, is "trampoline" data that wraps any Shape value, when Circle is just a Circle. That's why the two functions () ->Shape? and () ->Circle? don't match today.

But maybe they will eventually, thanks to your request!

Gwendal