[swift-evolution] Renaming for Protocol Conformance

Xiaodi Wu xiaodi.wu at gmail.com
Wed Aug 24 14:35:27 CDT 2016


On Wed, Aug 24, 2016 at 1:59 PM, Jonathan Hull <jhull at gbis.com> wrote:

>
> On Aug 24, 2016, at 7:48 AM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>
> On Wed, Aug 24, 2016 at 3:39 AM, Jonathan Hull <jhull at gbis.com> wrote:
>
>>
>> On Aug 23, 2016, at 8:35 AM, Xiaodi Wu <xiaodi.wu at gmail.com> 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.
>>
>>
>> Right now I commonly have to hand-namespace protocol methods/properties
>> to avoid conflicts.  So instead of having ‘var image:UIImage’ (which is the
>> name which makes the most sense in the protocol’s context), I have ‘var
>> protocolNameImage:UIImage’.  There are lots of things which have common
>> properties like ‘count’ which have to be called ‘somethingCount’ or
>> ‘countOfSomething’.  In the context of the protocol, these names are full
>> of redundant words (especially when measured against the new naming
>> guidelines).  We are all used to doing this for Objective C, but it feels
>> out of place in Swift.
>>
>> This will become a much more serious issue as the third-party code
>> ecosystem grows.  Without some capability like this, you will have
>> frameworks which can’t be used together (or at least with the same
>> object).  I would hate to see a ‘best practice’ emerge of adding 3 letter
>> prefixes to all protocol methods to get around compatibility issues.
>>
>
> Ah, well this isn't exactly the diamond problem you're talking about here.
> Instead, I think, we have a fundamental disagreement. I think I've been
> told that this opinion of mine is 'insane'--but I hold to it:
>
>
> Well you asked for an additional example besides the diamond problem… so
> no it isn’t.  I did include a diamond problem example further down though…
>

Sorry, I wasn't asking for an example _besides_ the diamond problem. I was
asking for more information about a concrete scenario, diamond problem or
not, where an existing technique could not resolve the conflict (for
instance, a scenario when you controlled neither of two conflicting
protocols, and where no satisfactory alternative design existed that could
avoid conforming a single type to both protocols).


> Protocols are not merely a vehicle for delivering a reusable bag of code.
> One of its most essential purposes is to constrain the shape or API of its
> conforming types. Therefore, it is a feature, not a bug, that with every
> choice of name in a protocol you foreclose the possibility of composing
> that protocol with others that might have colliding names.
>
> Currently, if you the protocol vendor have made the decision that `image`
> "makes the most sense in the protocol's context", you must have considered
> whether it would be absurd for a conforming type to have another use for
> `image`. If it would be absurd, then `image` is the appropriate name for
> your protocol requirement and any other word would truly be redundant. But,
> if this is only one of many plausible images, then `somethingImage` or
> `imageOfSomething` *is* the appropriate name, and trying to shorten the
> name isn't at all consistent with Swift guidelines but rather an incorrect
> attempt to prioritize brevity over clarity.
>
>
> Most things that conform would have ‘image’, and it would have exactly the
> same semantics as my protocol. Thus their ‘image’ would provide conformance
> without additional work.  But I have to worry about name collisions, so now
> I have to defensively call it ‘imageOfSomething', which they now have to
> implement to call their ‘image’ method.
>
>
> What you're arguing is that protocol designers should be able to design
> protocols without regard for how they will compose with others in
> conforming types, relying on a new member-renaming feature instead. But, as
> you point out, you can already use a protocol as a mere bag of code by
> naming all members with unique, prefixed names, then have conforming types
> forward their own choice of names to these.
>
>
> No, I am arguing that protocol authors should design protocols in the way
> which makes the behavior/semantics of the protocol the most obvious to the
> caller.  95% of the time there won’t be collisions, but occasionally there
> will be and we have to have a plan for that.
>
>
> This member-renaming feature you propose would enhance the aesthetic
> pleasure of the protocol designer, allowing simple names that don't ever
> have to appear in the public API of a concrete type to be used for a
> protocol member without placing any restrictions on the API of conforming
> types. However, I don't see anything wrong with the current hand-prefixing
> method being enshrined as "best practice" for the bag-of-code approach to
> protocols. If, as you predict, a growing third-party code ecosystem makes
> name collisions worse, then in fact having uniquely distinguishable
> prefixed members would be less confusing than having conforming types
> renaming protocol members as a matter of course.
>
>
> You are arguing that namespace collisions are a feature instead of a bug?
> Did you feel that way about ObjectiveC’s lack of name spacing?
>

My argument is about protocols specifically: I understand that a major
feature of protocols is that they make guarantees regarding the API of
conforming types. In rare cases, two guarantees may conflict, but I do not
consider that conflict to be a bug per se, as it is the inevitable result
of what it means to have guarantees, i.e. it is part and parcel of the
feature. In order to provide a way of resolving conflicting requirements in
protocols, your solution eliminates the API-guaranteeing feature of
protocols altogether.

I can't comment about Objective-C, because I've never written a single line
of it.


> I don’t think it is anywhere near as confusing as you suggest. As I
> mentioned before, if you cast it to the protocol, then the original names
> will still work.
>

Except when you can't cast to a protocol existential, as is the case with
any protocol with Self or associated type requirements.


> If you are trying to type the original name on the typed conformer, then
> showing the renamed version (with an indication of the renaming) in
> autocomplete should teach the change and clear up any confusion.
>

Mine isn't an argument about usability or learnability. It's a
philosophical/design point: what are protocols for? My answer: among other
uses, for constraining the API of conforming types. Perhaps this view is
incompatible with the view that protocols should support additional
mixin-like features.

To take your example of walk().  Perhaps we have a protocol ‘Walkable’
>> which refers to any data structure where the nodes can be walked using the
>> ‘walk()’ function.  It is easy to imagine two different protocols A & B
>> which specialize on this in different ways (say LinearWalkable &
>> RandomWalkable), and both add some methods/properties and use those to
>> provide efficient default implementations.  At some point, you may run into
>> a data structure which could easily be walked in both ways.
>>
>> As things are right now, you couldn’t inherit from both protocols.  While
>> you could add new ‘linearWalk()’ & ‘randomWalk()’ to the protocols
>> respectively (cluttering their interface), there is still the issue of what
>> to do when 'walk()’ is called.  You can’t rename walk() in the originating
>> protocols because it comes from their common ancestor.  Much better to
>> force one (or both) of the methods to be renamed on the conforming data
>> structure.  That keeps the interfaces of the protocols clean and makes the
>> options available on the data structure clearer (e.g. ‘walk()’ &
>> ‘randomWalk()’ )
>>
>> What I have had to do in the current version is inherit from the original
>> protocol and then copy and paste the default implementations from the
>> specialized versions.  Now my code has been duplicated and is harder to
>> maintain.  We can do better.
>>
>>
> I think Charles's solution is pretty nice, but he's right that the API
> surface area will have to grow. I don't know his original use case, so I
> don't know how ugly I'd find the final solution to be in that scenario. In
> this particular example, I'd say that having `linearWalk()` and
> `randomWalk()` distinguished seems pretty sensible and an overall win for
> clarity. If the same vendor controlled all three protocols, then `Walkable`
> could have the `walk()` requirement removed altogether for even more
> clarity.
>
>
> So you would remove 'walk()' from Walkable to avoid the name collision in
> this one case, when ‘walk()’ is Walkable’s entire reason for being?
>

No, `walk()` is not Walkable's entire reason for being. Protocols guarantee
semantics also. A `Walkable` protocol without any required members would
still have a reason for being: conforming types are walkable.

On the other hand, given that Walkable certainly would have associated type
requriements, if you considered that `walk()` _was_ Walkable's entire
reason for being *and* you could rename `walk()` in any conforming type,
how is that different from not having a `walk()` requirement at all?

Also, you would lose polymorphism.
>
>
>
> Also, just a hunch, but I suspect your hypothetical would never hold.
> Could you envision how the requirements for RandomWalkable might be such
> that it's possible to implement an efficient _default_ implementation of a
> random walk for any of several conforming data structures, but only one of
> these data structures is LinearWalkable, _and_ such a linear walk is
> efficient using another _default_implementation for an overlapping but not
> identical set of data structures? It's not mere trivia here, because the
> crux of your argument is that there exist default implementations that
> require copying and pasting into conforming types (and sufficiently
> efficient default implementations so that copying and pasting is
> appropriate rather than implementing a more efficient version). More likely
> in diamond problem scenarios, I think, colliding members are going to be
> properties or methods either without default implementations or than need
> to supply more efficient versions of default implementations anyway.
>
>
> Based on what?  There is a reason the diamond problem has a name (and a
> wikipedia entry).  Charles just said he ran into a problem like this.  I
> have run into it in the past as well.
>

Sure, and returning to my question above: could you share details about
where you've run into this?
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160824/d9432d6b/attachment.html>


More information about the swift-evolution mailing list