Renaming for Protocol Conformance

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`

I find it a little bit strange to require from a protocol designer to foresee all future uses of a protocol. IMO protocols are not bags of code but encapsulate a certain (typically fine grained) semantic. How this semantic will be coupled with other semantics (i.e. protocols) is widely open.

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.

This is a separate problem which will be solved once we have existential types.
Furhermore the argument still holds when the protocol is being used as type constrained.

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.

I don't think this has anything to do with mixins. It is just the general problem of being able to combine protocols which have been designed independently from each other.

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?

Generic methods using a type constrained by Walkable can call `walk()` on it. The same will be possible for variables declared with existential types in the future and it already applies for protocols without associated type or Self requirements.

-Thorsten

···

Am 24.08.2016 um 21:35 schrieb Xiaodi Wu via swift-evolution <swift-evolution@swift.org>:

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

On Aug 24, 2016, at 7:48 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Wed, Aug 24, 2016 at 3:39 AM, Jonathan Hull <jhull@gbis.com> wrote:

On Aug 23, 2016, at 8:35 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Tue, Aug 23, 2016 at 3:02 AM, Jonathan Hull <jhull@gbis.com> wrote:

On Aug 22, 2016, at 11:32 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Mon, Aug 22, 2016 at 11:59 PM, Jonathan Hull via swift-evolution <swift-evolution@swift.org> wrote:

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?

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

I think we agree on a lot of premises but come to opposite conclusions. I
would not phrase it precisely how you did, that protocol designers are
currently required 'to foresee all uses.' Rather, I see an analogy with
these scenarios:

a) Many functions in the standard library have preconditions for their
arguments. When those preconditions are violated, execution is halted. The
designer of the function writes a precondition to indicate the inputs for
which he or she believes the method produces a meaningful result. If the
preconditions are set too restrictively, then the caller of that function
will be frustrated to find crashing code.

But is this a feature or a bug? Well, it is a bug that the designer set
inappropriate preconditions. But the bug is in deficient *reasoning* by the
designer; the precondition accurately reflects a failure of the designer to
reason about a sensible use of the function. However, given that there has
been a defect in reasoning by the designer, is it a bug or a feature that
Swift refuses to execute code that hasn't been reasoned through? It is a
feature.

b) Classes now cannot be subclassed outside their modules by default. This
decision has been controversial, but it follows the same reasoning. The
designer of the function, if he or she has reasoned through the effects of
subclassing, can indicate that by making the class open. It may be the case
that a designer has not put in the thought work necessary and a class is
unnecessarily sealed. This would no doubt be frustrating.

Is that a bug or a feature? Again, it is a bug on the part of the designer,
for not having done the necessary thought work. But is it a bug or a
feature for Swift to refuse to compile code that hasn't been reasoned
through? I think it has been made clear that, as an opinionated language,
Swift makes the argument that this is a deliberate feature.

I argue the same thing applies here. Currently, protocols constrain the API
of conforming types. If a designer has reasoned through their design
correctly, then all is well. If a designer has made bad design choices,
then conforming types are constrained to have bad API design. Is that a bug
or a feature? It is of course a bug on the part of the designer for having
designed a bad protocol. But I think it is arguably a feature on the part
of Swift for refusing to allow code to circumvent the protocol's design,
because a protocol that is out of the end user's control is also likely a
protocol for which the default implementations are opaque to that user and
can't be reasoned through by that user.

I'm not entirely sure on my position, though. To be convinced otherwise,
I'd need to see a compelling real-world use case that demonstrates all of
the following:

- Safety: perhaps, with protocols, unlike class inheritance, it is almost
always safe to make these kinds of changes unanticipated by the original
author--it would be good to see evidence either way

- Necessity: earlier, examples were cited where at least one of two
conflicting protocols were under the user's control; in that case, this
feature isn't needed, because that protocol's requirements can be trivially
renamed

- Good design: show me that the design patterns enabled by the feature are
better than what is possible with workarounds; if a renaming or hiding
feature is expedient, but an alternative approach would promote less
fragile or more Swifty design, then we should be exploring ways to simplify
or promote an alternative approach; one consideration, for example, is that
naming of anything is hard, and the current fact that protocols *need* to
have well-chosen member names encourages designers to think hard--but if
renaming becomes possible, could we simply be enabling less thoughtful
protocol design with little benefit (to evaluate this, we would need some
sense of the answer to the necessity question above)?

···

On Fri, Sep 2, 2016 at 10:56 Thorsten Seitz <tseitz42@icloud.com> wrote:

Am 24.08.2016 um 21:35 schrieb Xiaodi Wu via swift-evolution < > swift-evolution@swift.org>:

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

On Aug 24, 2016, at 7:48 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Wed, Aug 24, 2016 at 3:39 AM, Jonathan Hull <jhull@gbis.com> wrote:

On Aug 23, 2016, at 8:35 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Tue, Aug 23, 2016 at 3:02 AM, Jonathan Hull <jhull@gbis.com> wrote:

On Aug 22, 2016, at 11:32 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Mon, Aug 22, 2016 at 11:59 PM, Jonathan Hull via swift-evolution < >>>> swift-evolution@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`

I find it a little bit strange to require from a protocol designer to
foresee all future uses of a protocol. IMO protocols are not bags of code
but encapsulate a certain (typically fine grained) semantic. How this
semantic will be coupled with other semantics (i.e. protocols) is widely
open.

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.

This is a separate problem which will be solved once we have existential
types.
Furhermore the argument still holds when the protocol is being used as
type constrained.

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.

I don't think this has anything to do with mixins. It is just the general
problem of being able to combine protocols which have been designed
independently from each other.

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?

Generic methods using a type constrained by Walkable can call `walk()` on
it. The same will be possible for variables declared with existential types
in the future and it already applies for protocols without associated type
or Self requirements.

-Thorsten

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?

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

I agree. Why should a language that supports:

1. type aliases and retroactive modeling (even adding protocol conformance to existing types to the extent that basic types implement user library protocols) and
2. discuss to add type aliases for generic types and existentials as well as
3. something like *new type* for type aliases with nominal typing...

not allow to rename methods? The argument that protocols have to be designed by the "protocol vendor" foreseeing all its usages does not hold for every case. A language with type aliases and retroactive modeling allows for domain specificity via context dependent changes to a and renaming of a type. This can be used for internal DSLs (domain specific languages) and the like.

I know that my point here is orthogonal to the original problem of renaming because of name clashes between protocols, but I address the more general question whether renaming of associated types, methods and the like wouldn't be a good (and consequent) thing.

All the best
Johannes

···

Von meinem iPhone gesendet

Am 02.09.2016 um 17:56 schrieb Thorsten Seitz via swift-evolution <swift-evolution@swift.org>:

Am 24.08.2016 um 21:35 schrieb Xiaodi Wu via swift-evolution <swift-evolution@swift.org>:

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

On Aug 24, 2016, at 7:48 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Wed, Aug 24, 2016 at 3:39 AM, Jonathan Hull <jhull@gbis.com> wrote:

On Aug 23, 2016, at 8:35 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Tue, Aug 23, 2016 at 3:02 AM, Jonathan Hull <jhull@gbis.com> wrote:

On Aug 22, 2016, at 11:32 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Mon, Aug 22, 2016 at 11:59 PM, Jonathan Hull via swift-evolution <swift-evolution@swift.org> wrote:

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`.

I find it a little bit strange to require from a protocol designer to foresee all future uses of a protocol. IMO protocols are not bags of code but encapsulate a certain (typically fine grained) semantic. How this semantic will be coupled with other semantics (i.e. protocols) is widely open.

Von meinem iPhone gesendet

I argue the same thing applies here. Currently, protocols constrain the API of conforming types. If a designer has reasoned through their design correctly, then all is well. If a designer has made bad design choices, then conforming types are constrained to have bad API design. Is that a bug or a feature? It is of course a bug on the part of the designer for having designed a bad protocol. But I think it is arguably a feature on the part of Swift for refusing to allow code to circumvent the protocol's design, because a protocol that is out of the end user's control is also likely a protocol for which the default implementations are opaque to that user and can't be reasoned through by that user.

There seem to be two schools of thought in this community: Those that want to empower good design, and those that want to prevent/punish bad design.

They often look similar, but they are subtly different. One treats the programmer as an intelligent adult, who may make mistakes, and would appreciate reminders about the possibility of problems. The other treats the programmer as a child who must be protected from themselves. I am strongly in the first camp. We should provide warnings and force conflicts to be resolved in some way, but ultimately we need to trust the programmer to do the right thing. Also, as people kept saying during the 'closed by default' discussion, if they screw up and rename something in a confusing way, then other people will complain to them and they will change it.

I'm not entirely sure on my position, though. To be convinced otherwise, I'd need to see a compelling real-world use case that demonstrates all of the following:

- Safety: perhaps, with protocols, unlike class inheritance, it is almost always safe to make these kinds of changes unanticipated by the original author--it would be good to see evidence either way

You may find this paper interesting:

It discusses traits, which are basically Swift’s protocols, plus the ability to handle conflicts. It discusses at length the issues that arise with other systems.

This is a very common problem that only a few programming languages have found successful solutions to. I would also point you to Eiffel’s model for multiple inheritance (select, rename, undefine, export). I think if we keep working on it, we can find a uniquely swift-feeling solution which solves the issue :-)

- Necessity: earlier, examples were cited where at least one of two conflicting protocols were under the user's control; in that case, this feature isn't needed, because that protocol's requirements can be trivially renamed

I ran into a case of the diamond problem with protocols again today in a real world project. Both classes were under my control, but I wasn’t able to avoid the name conflict because they had a common ancestor. I was able to solve it by copy/pasting the winning method definition, but this is definitely not ideal, since I must now maintain code in two places which makes upkeep/changes more difficult.

What I want here is the ability to notate the winner in my inheriting protocol.

- Good design: show me that the design patterns enabled by the feature are better than what is possible with workarounds; if a renaming or hiding feature is expedient, but an alternative approach would promote less fragile or more Swifty design, then we should be exploring ways to simplify or promote an alternative approach; one consideration, for example, is that naming of anything is hard, and the current fact that protocols *need* to have well-chosen member names encourages designers to think hard--but if renaming becomes possible, could we simply be enabling less thoughtful protocol design with little benefit (to evaluate this, we would need some sense of the answer to the necessity question above)?

Your suggestion that the framework designers should have to go back and refactor their code because of another framework is an indication of coupled design and bad code smell. Suddenly my protocols have to know about all of the other protocols being used (or which could theoretically be used) and I have to design around them. This is problematic. Ideally, I should be able to design each protocol in a way which makes the most sense semantically for the use of that protocol. Any contortions I have to make to the design away from the ideal are something to be avoided where possible. I know we are used to making these contortions, but is it really the best design (or is it a form of Stockholm syndrome)?

Here is an article on Eiffel’s multiple inheritance system which makes the point well:

Some selected passages:

One of the differences is particularly important if you take a software engineering viewpoint and consider these solutions in relation with the software development lifecycle. If the developers first implement simple menus only, and only then realize the need for walking menus, using tree solution means that they must go back to the initial definition of class MENU and rework it extensively. This brings disorder into the development process.

With the multiple inheritance solution, things are quite different. When the developers realize that a new kind of menu is needed, they simply define it by inheritance from MENU and ENTRY. The changes to existing software are minimal. This is an example of one of the key benefits of the object-oriented method, which would have to be renounced in the absence of multiple inheritance.

(emphasis mine)

Assume you are in the inheritance-based reusability business and combine classes from two software suppliers, say one in New York and one in London. Sure enough, one day you are going to run into the problem of combining two classes that both have a feature called foo, to use as example one of these nice evocative names that programmers are fond of.

But this is not a conceptual problem! It merely results from an unfortunate choice of names. If the parent programmers had chosen other names, differing by just one letter -- influenced perhaps by local conditions, they might have used zoo in New York and fog in London --, the problem would never have arisen.

This suggests the two key observations on this problem:

First, it is purely a syntactical problem, due to conflicting name choices. It has nothing to do with the fundamental properties of the classes involved.
Second, nothing is wrong with the parents; each is perfectly consistent as it stands. The "culprit" is the common heir, which tries to combine two classes that are incompatible as they stand. So the heir should also be responsible for the solution.

It also includes an example of how renaming can make a design more consistent. You can use a protocol for it’s functionality (Tree-based calculation) while providing an end-user interface that makes sense to your domain.

Although purely syntactical, renaming is not limited in its applications to the resolution of name clashes. Among its other uses, one deserves a particular mention because it sheds more light into the meaning and power of inheritance. This is renaming used to define consistent class interfaces.

The following example is typical. Again, it will illustrate techniques used in the Eiffel Graphical Library. Assume we want to describe rectangular windows that can be arbitrarily nested. The corresponding class, say WINDOW, will have a large number of features, which can be grouped into two categories:

Graphical features: height, width, position, move, scale and the like.
Hierarchical features, having to do with the tree structure of nested windows: add_subwindow, remove_subwindow, superwindow and the like.

[…]

Features inherited from TREE, in particular, will bear their tree names: insert_node, remove_node, parent_node and so on. This is not acceptable to a client programmer like Ed who wants good, concrete window terminology.

With renaming, the solution is straightforward. Wendy ought to spend ten more minutes polishing the interface, so that clients of class WINDOW won't need to be aware of its tree ancestry:

  class WINDOW inherit

    RECT_SHAPE
      rename ...

    TREE
      rename
        insert_node as add_subwindow,
        remove_node as delete_subwindow,
        parent_node as superwindow,
        ...
  feature

    ...

  end -- class WINDOW

I think we have an opportunity here to actually solve this problem by looking at the solutions which came before and then creating something uniquely swift.

Thanks,
Jon

Here’s another obscure paper coming from the world of C++, see section 3.5/4.1:
http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.48.129

Not a syntax that we would want to emulate, but it’s the same idea in different clothes. The Swift implementation model should support implementing something along these lines, but it would be a purely additive feature.

-Chris

···

On Sep 6, 2016, at 6:06 PM, Jonathan Hull via swift-evolution <swift-evolution@swift.org> wrote:

I'm not entirely sure on my position, though. To be convinced otherwise, I'd need to see a compelling real-world use case that demonstrates all of the following:

- Safety: perhaps, with protocols, unlike class inheritance, it is almost always safe to make these kinds of changes unanticipated by the original author--it would be good to see evidence either way

You may find this paper interesting:
http://scg.unibe.ch/archive/papers/Scha02bTraits.pdf

It discusses traits, which are basically Swift’s protocols, plus the ability to handle conflicts. It discusses at length the issues that arise with other systems.

This is a very common problem that only a few programming languages have found successful solutions to. I would also point you to Eiffel’s model for multiple inheritance (select, rename, undefine, export). I think if we keep working on it, we can find a uniquely swift-feeling solution which solves the issue :-)

I argue the same thing applies here. Currently, protocols constrain the
API of conforming types. If a designer has reasoned through their design
correctly, then all is well. If a designer has made bad design choices,
then conforming types are constrained to have bad API design. Is that a bug
or a feature? It is of course a bug on the part of the designer for having
designed a bad protocol. But I think it is arguably a feature on the part
of Swift for refusing to allow code to circumvent the protocol's design,
because a protocol that is out of the end user's control is also likely a
protocol for which the default implementations are opaque to that user and
can't be reasoned through by that user.

There seem to be two schools of thought in this community: Those that
want to empower good design, and those that want to prevent/punish bad
design.

They often look similar, but they are subtly different. One treats the
programmer as an intelligent adult, who may make mistakes, and would
appreciate reminders about the possibility of problems. The other treats
the programmer as a child who must be protected from themselves. I am
strongly in the first camp.

I think this is quite an unfair characterization. When a programming
language can guarantee that certain things won't occur, it isn't about
punishing anyone; rather, those who have to read the code and reason about
its behavior gain tangible benefits from such constraints.

We should provide warnings and force conflicts to be resolved in some way,
but ultimately we need to trust the programmer to do the right thing. Also,
as people kept saying during the 'closed by default' discussion, if they
screw up and rename something in a confusing way, then other people will
complain to them and they will change it.

I'm not entirely sure on my position, though. To be convinced otherwise,
I'd need to see a compelling real-world use case that demonstrates all of
the following:

- Safety: perhaps, with protocols, unlike class inheritance, it is almost
always safe to make these kinds of changes unanticipated by the original
author--it would be good to see evidence either way

You may find this paper interesting:
http://scg.unibe.ch/archive/papers/Scha02bTraits.pdf

It discusses traits, which are basically Swift’s protocols, plus the
ability to handle conflicts. It discusses at length the issues that arise
with other systems.

Thanks for the link. I'll need to study this further. For the moment, one
initial thought: one key point in this paper is that conflict resolution
for traits hinges on the fact that they do not access state, and so the
diamond problem does not occur.

This is a very common problem that only a few programming languages have
found successful solutions to. I would also point you to Eiffel’s model
for multiple inheritance (select, rename, undefine, export). I think if we
keep working on it, we can find a uniquely swift-feeling solution which
solves the issue :-)

- Necessity: earlier, examples were cited where at least one of two
conflicting protocols were under the user's control; in that case, this
feature isn't needed, because that protocol's requirements can be trivially
renamed

I ran into a case of the diamond problem with protocols again today in a
real world project. Both classes were under my control, but I wasn’t able
to avoid the name conflict because they had a common ancestor.

Can you explain this in more detail? Were you working with protocols or
classes?

If a protocol inherits from two protocols with a common ancestor, the
protocol requirements from that ancestor cannot possibly conflict with
themselves. Are you talking about default implementations? If so, there's
another thread (recent, but dormant now) suggesting new syntax to call a
named default implementation from an implementation in a conforming type
(and by extension (har har), from a default implementation in an inheriting
protocol). I believe that the suggestion had a pretty positive reception,
and unless I'm mistaken, it would address this particular issue (which I
agree is currently problematic).

I was able to solve it by copy/pasting the winning method definition, but
this is definitely not ideal, since I must now maintain code in two places
which makes upkeep/changes more difficult.

What I want here is the ability to notate the winner in my inheriting
protocol.

- Good design: show me that the design patterns enabled by the feature are
better than what is possible with workarounds; if a renaming or hiding
feature is expedient, but an alternative approach would promote less
fragile or more Swifty design, then we should be exploring ways to simplify
or promote an alternative approach; one consideration, for example, is that
naming of anything is hard, and the current fact that protocols *need* to
have well-chosen member names encourages designers to think hard--but if
renaming becomes possible, could we simply be enabling less thoughtful
protocol design with little benefit (to evaluate this, we would need some
sense of the answer to the necessity question above)?

Your suggestion that the framework designers should have to go back and
refactor their code because of another framework is an indication of
coupled design and bad code smell. Suddenly my protocols have to know
about all of the other protocols being used (or which could theoretically
be used) and I have to design around them. This is problematic. Ideally,
I should be able to design each protocol in a way which makes the most
sense semantically for the use of that protocol. Any contortions I have to
make to the design away from the ideal are something to be avoided where
possible. I know we are used to making these contortions, but is it really
the best design (or is it a form of Stockholm syndrome)?

I see this differently. Are you arguing that protocols should be designed
*without* consideration of how they compose with other protocols? By the
same token, should types be designed *without* consideration of how they
are used? If users of my library (which might be just myself, dogfooding)
find that the API is cumbersome, are you arguing that I *shouldn't* go back
and redesign the API for the next major version, because I'd be coupling
the design of my API to the needs of my end users?

To use your analogy, what you characterize as grotesque contortions are
what I'm arguing is a graceful dance.

Here is an article on Eiffel’s multiple inheritance system which makes the
point well:
ISE technology papers
bmarticles/joop/multiple.html

Some selected passages:

One of the differences is particularly important if you take a software
engineering viewpoint and consider these solutions in relation with the
software development lifecycle. If the developers first implement simple
menus only, and only then realize the need for walking menus, using tree
solution means that they must go back to the initial definition of class
*MENU* and rework it extensively. This brings disorder into the
development process.

With the multiple inheritance solution, things are quite different. When
the developers realize that a new kind of menu is needed, they simply
define it by inheritance from *MENU* and *ENTRY*. The changes to existing
software are minimal. This is an example of one of the key benefits of the
object-oriented method, which would have to be renounced in the absence of
multiple inheritance.

(emphasis mine)

Assume you are in the inheritance-based reusability business and combine
classes from two software suppliers, say one in New York and one in London.
Sure enough, one day you are going to run into the problem of combining two
classes that both have a feature called *foo*, to use as example one of
these nice evocative names that programmers are fond of.

But this is not a conceptual problem! It merely results from an
unfortunate choice of names. If the parent programmers had chosen other
names, differing by just one letter -- influenced perhaps by local
conditions, they might have used *zoo* in New York and *fog* in London
--, the problem would never have arisen.

This suggests the two key observations on this problem:

   - First, it is purely a *syntactical* problem, due to conflicting name
   choices. It has nothing to do with the fundamental properties of the
   classes involved.
   - Second, *nothing is wrong with the parents*; each is perfectly
   consistent as it stands. The "culprit" is the common heir, which tries to
   combine two classes that are incompatible as they stand. So *the heir
   should also be responsible for the solution*.

Unless I'm mistaken, the generally accepted wisdom that's emerged since the
publication of that article in 1988 is that multiple inheritance is perhaps
not the wisest choice. Today, Swift supports OOP but eschews multiple
inheritance. To the extent that we extend Swift's support of POP, it should
not be to restore the same pitfalls of multiple inheritance under the guise
of a different paradigm.

It also includes an example of how renaming can make a design more
consistent. You can use a protocol for it’s functionality (Tree-based
calculation) while providing an end-user interface that makes sense to your
domain.

Again, I understand the motivation. My point is, currently, conformance to
a protocol guarantees a certain interface for the conforming type. Adding
the ability to use a protocol for its functionality *without* being
constrained to provide an interface *necessarily entails the loss* of a
certain feature of protocol conformance today: namely, the guarantee of
that interface on the concrete type.

Although purely syntactical, renaming is not limited in its applications
to the resolution of name clashes. Among its other uses, one deserves a
particular mention because it sheds more light into the meaning and power
of inheritance. This is renaming used to define consistent class interfaces.

The following example is typical. Again, it will illustrate techniques
used in the Eiffel Graphical Library. Assume we want to describe
rectangular windows that can be arbitrarily nested. The corresponding
class, say *WINDOW*, will have a large number of features, which can be
grouped into two categories:

   - Graphical features: *height, width, position, move, scale* and the
   like.
   - Hierarchical features, having to do with the tree structure of
   nested windows: *add_subwindow, remove_subwindow, superwindow* and the
   like.

[…]

Features inherited from *TREE*, in particular, will bear their tree
names: *insert_node, remove_node, parent_node* and so on. This is not
acceptable to a client programmer like Ed who wants good, concrete window
terminology.

With renaming, the solution is straightforward. Wendy ought to spend ten
more minutes polishing the interface, so that clients of class *WINDOW* won't
need to be aware of its tree ancestry:

  *class** WINDOW* *inherit*

    *RECT_SHAPE*
      *rename* ...

    *TREE*
      *rename**
        insert_node as add_subwindow,
        remove_node as delete_subwindow,
        parent_node as superwindow,
        ...
  **feature*

    ...

  *end* -- class *WINDOW*

I think we have an opportunity here to actually solve this problem by
looking at the solutions which came before and then creating something
uniquely swift.

Doesn't the same quotation argue for multiple inheritance of classes and
renaming of class members in subclasses, neither of which are supported in
Swift? Are you proposing to have those features too?

In this example, Wendy renames members on WINDOW for the express purpose of
obscuring its ancestry. This makes Ed happy because he likes particular
names for methods on WINDOW. However, it certainly makes it more difficult
to reason about what's actually going on when you call these methods. Where
is add_subwindow implemented? You won't find it anywhere in the code. This
is not an unmitigated win. It's a mixed bag at best; IMO, we're better off
without such a feature.

Thanks,

···

On Tue, Sep 6, 2016 at 8:06 PM, Jonathan Hull <jhull@gbis.com> wrote:

Jon