[swift-evolution] Renaming for Protocol Conformance
jhull at gbis.com
Tue Sep 6 20:06:31 CDT 2016
> 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.
> 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
> rename ...
> insert_node as add_subwindow,
> remove_node as delete_subwindow,
> parent_node as superwindow,
> 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.
-------------- next part --------------
An HTML attachment was scrubbed...
More information about the swift-evolution