[swift-evolution] Renaming for Protocol Conformance

Xiaodi Wu xiaodi.wu at gmail.com
Tue Sep 6 23:32:25 CDT 2016


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

>
> 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:
> https://archive.eiffel.com/doc/manuals/technology/
> 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,
> Jon
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160906/5807a3c4/attachment.html>


More information about the swift-evolution mailing list