Hi Everyone,

I have been mulling over the problems of optional protocol methods and 
(separately) the mixin / diamond problem over in my head for a week or so now, 
and I wonder if they might have a common solution.

The main philosophical dispute seems to be around what we call the "system 
image” in User Experience.  The folks from the C++ & Java world are most 
comfortable when all of the levers and gears are openly exposed because it 
allows them to better reason about and predict the system’s behavior.  Those 
from the Smalltalk/Python/ObjC world want progressive disclosure, where they 
don’t really have to even think about the minutiae unless it becomes important, 
so they can focus on their solution instead of abstract mathematics.  When it 
becomes important, then they want to be able to access all of those levers, but 
before that, they should be hidden away and everything should "just work”.  
(This is why ObjC had only 3 main collections hiding implementations behind 
class clusters and Java has things like “ConcurrentSkipListMap”)

Swift has been charting the waters between these two worlds, trying to provide 
for both where possible, and I nothing I say today will unify those 
philosophical differences.  I might however, have a swift-ier way to provide 
the functionality/flexibility that optional methods gave us in ObjC without 
actual optional methods.


The ability to provide default protocol implementations gives us most of the 
benefits of Optional methods on the protocol provider’s side.  If there is a 
default implementation, you only need to provide an override if you need 
special behavior.  There are suddenly a bunch of functions you don’t have to 
worry about unless you need them… progressive disclosure. ObjC folks are 
relatively happy (and it still allows a good deal of compiler optimization to 
boot).

The one issue is on the caller’s side we no longer have information about 
whether the protocol implementor overrode the default or not.  In ObjC, it was 
common to use the information of whether an implementation was provided to 
optimize things behind the scenes.  We could provide a slow implementation for 
the general case, but then provide much faster implementations when we had the 
guarantee that a certain option/feature would not be used.  Several people have 
suggested adding an explicit extra method where the programmer can signal their 
intent, but this now allows for new errors.  Before we had a compiler/runtime 
guarantee that a feature wasn’t used because we know the code to use it doesn’t 
exist.  The explicit extra method can now get out of sync with the feature 
implementation (the programmer might forget to signal or they might forget to 
remove the signal after refactoring).  There is a much larger API to learn, and 
there is more to go wrong…

That said… I think that we could remove optional methods (using Douglas’ 
backwards compatible proposal for ObjC code) and replace them with default 
implementations (with the understanding that we will get our optimization 
capabilities back in a moment using a different mechanism).  I think everyone 
also agrees that we need to mark the generated interfaces of methods which do 
not have default implementations as “required” (or something similar).  

Ok, so now we always know we can call a method in the protocol and we know it 
will have an implementation… no need to check if it is there first.  The 
language just became a little bit simpler and more consistent.


Now let’s turn to another issue which has come up from time to time.  It would 
be nice, when overriding a protocol's default implementation, to be able to 
call the default implementation (kind of like calling super in a class).  
Similarly, when we end up providing mixins, we will need a way to disambiguate 
and select between the implementations.  I have also occasionally run into a 
case or two where I want to be able to call a specific ancestor of a class 
(e.g. super’s super) and get that implementation (I used to use a language 
where that was easy, and I always missed it in ObjC).

In short, we end up with several different cases where we want to be able to be 
able to define which implementation we are calling among several options.  In 
classes, we use super, but with mixins and protocols, it can be much more 
complex.  We really just want to be able to explicitly state which 
implementation to use.  There are lots of syntax options being thrown about 
(e.g. 
'implements P’).  I will use the throwaway syntax of ‘instance.P::method’ just 
so we have something concrete to talk about (I don’t expect that to be the 
actual syntax).  That statement means that ‘method’ is called on ‘instance’ 
using the implementation in ‘P’.  Again, there is better syntax, but it will 
need those three pieces of information.

Now for the punchline.  

We know that we can always call a method on a protocol because we know there is 
an implementation for it somewhere.  Different methods may have been provided 
in various places (and some in several places), but together they cover the 
contract.  Once we start referencing specific implementors though, that 
guarantee doesn’t always hold (especially if we allow P to be generic).  The 
compiler still knows who implemented what of course, but we can’t just assume 
that every implementor of a protocol has overridden a particular default, and 
we shouldn’t fall back to the default when we are asking for a particular 
implementation.

The answer, of course, is to treat those cases exactly how we treat optional 
methods today.  We can just use optional chaining, etc… and the compiler will 
force us to deal with the cases where we can’t guarantee that the method 
exists.  We have gained back the missing capabilities of optional methods with 
the important difference that we don’t need to tag methods as “optional” and we 
are guaranteed that we can always call ‘instance.method’ safely.  It is only in 
the cases where we are asking for a particular implementation that we have to 
worry about the added complexity.  Progressive disclosure.

At the same time, we have also gained the ability to reference a protocol’s 
default implementation from an override and to disambiguate between competing 
protocol implementations.

Thoughts?  A direction worth exploring further?

Thanks,
Jon



_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to