[swift-evolution] Renaming for Protocol Conformance
Callionica (Swift)
swift-callionica at callionica.com
Tue Aug 23 17:51:17 CDT 2016
C# has this feature (called "explicit interface implementation" or
"explicit interface method implementation"). It's useful for implementing
multiple data-like interfaces that have common names like Name, ID, Title,
and Description. It's useful for keeping public interfaces clean. It's
useful for API evolution: sometimes you need to implement both IVersion1
and IVersion2 interfaces that have (as you would expect) similar method
names and semantics, but differences that still require different
implementations.
There's a stack overflow topic which has various perspectives on it:
http://stackoverflow.com/questions/143405/c-sharp-interfaces-implicit-implementation-versus-explicit-implementation
On Tue, Aug 23, 2016 at 8:35 AM, Xiaodi Wu via swift-evolution <
swift-evolution at swift.org> wrote:
> On Tue, Aug 23, 2016 at 3:02 AM, Jonathan Hull <jhull at gbis.com> wrote:
>
>>
>> On Aug 22, 2016, at 11:32 PM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>>
>> On Mon, Aug 22, 2016 at 11:59 PM, Jonathan Hull via swift-evolution <
>> swift-evolution at swift.org> wrote:
>>
>>> Hi everyone,
>>>
>>> We talked about this before when we were discussing mixins, and there
>>> seemed to be generally positive feelings towards it as a feature for the
>>> future.
>>
>>
>> It's been some time now since the original discussion, so perhaps you
>> could refresh our collective memory (or at least, mine): although it
>> *seems* like this feature might be useful, I can't recall a concrete use
>> case where I've felt like I needed this feature--do you have some examples?
>>
>>
>> Ideally, the biggest use is that it helps to (partially) solve the
>> diamond problem (and similar issues) by forcing/allowing disambiguation
>> when there are multiple protocols being conformed to. This will become
>> more of an issue if we allow protocols or extensions to add storage. Your
>> proposed syntax actually does a better job of it than mine because mine was
>> always shown as attached to some sort of implementation, whereas yours
>> could potentially allow access to a default implementation under a new name.
>>
>> Other than that, it generally allows us to bypass/mitigate conflicts
>> between protocols. In the current version, you are unable to conform to
>> both protocols (either because it won’t compile or because you can’t
>> satisfy the semantics of both protocols) without designing the protocols
>> together to avoid conflicts. (I have definitely had to go back and
>> rename/refactor properties on a protocol for this reason… which I couldn’t
>> have done if I didn’t control both protocols).
>>
>
> I understand something of the difficulty of confronting the diamond
> problem. As I wrote above, I'm inclined to believe that this proposed
> feature would help solve a real issue. However, the point I'm trying to
> make is that, on reflection, I have never actually been hampered by the
> lack of this feature, and so I'd like to continue the discussion to get a
> fuller sense of just how impactful this proposal would be, both positive
> and negative.
>
> It's true, of course, that if you control at least one of two protocols
> (you don't need to control both protocols), it is trivially easy to cause
> this problem to occur, but as you point out it is also possible to resolve
> the problem by re-designing the protocol you control. I'm inclined to think
> (without evidence, admittedly) that re-designing to remove the conflict,
> where possible, would actually be the superior option in most cases.
>
> My question was: have you actually run into a scenario that necessitates
> the feature you propose because you controlled neither conflicting
> protocol? I think it would strengthen the proposal greatly to have a
> concrete, uncontrived example.
>
> Take a look at Eiffel’s ‘rename’ & ’select’ features for similar
>> functionality and use-cases.
>>
>> Ultimately, this is a step in the direction of having true mixins.
>>
>>
> Sure, maybe. I couldn't evaluate that claim. I'm inclined to favor the
> proposal, but it'd have to stand on its own merits, not as a step to an
> as-yet undesigned feature.
>
> I am fairly certain this affects the ABI though, so I thought I would
>> bring it up now.
>>
>>>
>>> If two protocols have methods/properties with the same name, but
>>> different signatures, we need a way to distinguish between them when
>>> attempting to conform to both.
>>>
>>> protocol A {
>>> var x:Int {get set}
>>> }
>>>
>>> protocol B {
>>> var x:Double {get set}
>>> }
>>>
>>
>> Methods can be overloaded that differ in arguments or return type, so it
>> seems like this problem mainly exists with *properties* that differ in
>> type--am I wrong?
>>
>>
>> There is also the case of functions with the same name and signature, but
>> different semantics. There may be no single implementation which
>> simultaneously satisfies the semantics for both protocols. By renaming one
>> of the functions, we are able to provide separate implementations for each
>> requirement (which allows both protocols to function as intended).
>>
>
> True. However, putting on my critical hat, this seems like we're
> stretching to provide support for an anti-pattern. It'd be nice to have an
> example where one runs into the motivating problem *and* where the proposed
> feature promotes a _better_ design than is currently possible, rather than
> making a bad design compile.
>
> At this point, I'm imagining scenarios where a user is trying to conform a
> type MyAnimal to both Biped and Quadruped, then worrying that `walk()` has
> two semantics: something has already gone deeply wrong IMO.
>
> [Yes, I know there are animals that can sometimes walk on two or four
> legs. The point here is that the protocols were clearly designed to model
> animals at a certain level of detail, while it appears that the user
> writing `MyAnimal` wants to model the animal at a different level of detail
> than either protocol was designed to handle. You might have specific qualms
> about this particular hypothetical, but I think you can pick out the
> general point that there is a much larger problem inherent in the design
> than the specific problem regarding two colliding method signatures.]
>
> There may also be functions in different protocols with different names
>> but the same semantics and signature. This will allow a single
>> implementation to satisfy both protocols without duplication.
>>
>
> This is a poor argument IMO. You can already implement foo() and then have
> bar() forward to foo() with trivial effort and really minimal boilerplate.
> It's an existing solution, and a more general solution because it doesn't
> require matching signatures. Also, it's a better solution IMO because it
> preserves the notion that a type T : Fooable, Barrable provides the full
> API guaranteed by Fooable and Barrable.
>
> Finally, we may want to rename an inherited default implementation to
>> avoid conflicting with another protocol's default implementation in cases
>> where we don’t want to override it.
>>
>
> Yes, I think this would be handy. I can't think of an existing way to do
> this, and I expect it might make a big difference in designing good
> protocols. So here, I think we have a strong argument.
>
> Again, though, could we find a concrete example of how this feature would
> promoter a better design of an actual type and/or protocol?
>
> One possibility is to allow a struct/class/enum to conform to the
>> protocol while renaming one (or both) of the clashing methods:
>>
>>>
>>> struct C: A,B {
>>> var x:Int
>>> var y:Double implements B.x
>>> }
>>>
>>> The conforming method/property would still have to have the same
>>> signature, but could have a different name (and parameter labels). It
>>> would also allow protocol methods which have identical signatures and
>>> semantics, but different names to be implemented using the same method (i.e
>>> ‘implements D.z & E.w’).
>>>
>>> When something is cast to the protocol (say ‘as B’), then calling the
>>> property (e.g. ‘x’) would end up calling the implementation of the renamed
>>> property ( ‘y’ in this example) on the conforming type.
>>>
>>
>> Reflecting on this proposed change, it occurs to me that something of
>> value would be lost, and I think that this something is actually rather
>> valuable:
>>
>> Today, when I see that a type conforms to (for example) Sequence, I know
>> that certain methods and/or properties exist on that type. Protocol
>> conformance guarantees a certain API, not just certain semantics.
>>
>>
>> It isn’t actually lost, however. When working with it as a Sequence (for
>> example), that API would be intact using the original names. It is only
>> when working with it as its own type that the renaming would have an effect.
>>
>
> That is not my point. With this proposal, knowing that MyGreatType
> conforms to Sequence would no longer yield any information as to the
> MyGreatType API. That is definitely something lost and we should
> acknowledge that.
>
> Also, recall that Sequence has Self or associated type requirements. So:
>
> ```
> let m = MyGreatType()
> // There is nothing I can write here to use the Sequence API with `m`,
> IIUC;
> // however, depending on how this feature is designed, I *might* be able to
> // call a generic function that operates on a type `T : Sequence` and work
> // with `m` that way.
> ```
>
> Perhaps one way to mitigate this loss would be to have any renamed members
>> listed *in the declaration of conformance*, something like this (with some
>> additional bikeshedding):
>>
>> ```
>> struct MyGreatType : Sequence (count => length) {
>> // MyGreatType conforms to Sequence but renames `count` to `length`
>> }
>> ```
>>
>>
>> Yes, putting it in the conformance declaration is a definite possibility
>> we should consider.
>>
>>
>> I think we would also want a way to retroactively conform using existing
>>> properties/methods in an extension declaring conformance. Not sure what
>>> the best syntax for that would be. Off the top of my head (though I would
>>> love to have something with less cruft):
>>>
>>> extension D:B {
>>> @conform(to: B.x, with: D.y)
>>> }
>>>
>>> or maybe just:
>>>
>>> extension D:B {
>>> D.y implements B.x
>>> }
>>>
>>
>> If renamed members are declared along with protocol conformance, then the
>> syntax for retroactive modeling follows naturally:
>>
>> ```
>> extension D : B (x => y) { }
>> // again, the actual notation here is ugly
>> // but the underlying idea, I think, is worth considering
>> ```
>>
>>
>> Yup
>>
>> One thing I like about this is that it helps to solve the diamond
>> problem. ‘x’ could be a default implementation in B which D does not
>> override. I think this is an important case which my original proposal
>> didn’t address fully.
>>
>> We should keep bikeshedding the syntax though...
>>
>> Thanks,
>> Jon
>>
>>
>>
>
> _______________________________________________
> 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/20160823/5f92113d/attachment.html>
More information about the swift-evolution
mailing list