[swift-evolution] [Idea] Extend "required" to methods other than init()

Matthew Johnson matthew at anandabits.com
Mon Jan 18 09:19:55 CST 2016


> On Jan 17, 2016, at 5:54 PM, Jesse Squires via swift-evolution <swift-evolution at swift.org> wrote:
> 
> Hey all — this is my first reply to this list, so please forgive me if something goes wrong.
> 
> I wanted to elaborate more on Nate Birkholz's earlier message about extending the 'required' keyword to class methods, other than only init. 
> 
> I had suggested this to Joe Groff on Twitter, and encouraged Nate to go ahead and start the thread. I’m just now finding the time to respond :) My goal here is to flesh out this idea a bit more, and hopefully get a more engaging discussion started.
> 
> Subclassing in Objective-C has the following deficiencies:
> 
> (1) It lacks the ability to formally declare if a method must be overridden.
> 
> (2) When overriding a method, there is no mechanism to enforce a call to super. You could use NS_REQUIRES_SUPER, but not only is this barely discoverable, it isn’t part of the language. Further, it merely generates a warning when library authors most likely intend for this to be an error. (Ok, one could treat warnings as errors, etc. etc.)
> 
> (3) It is easy for clients to override a superclass method without knowing it. That is, subclasses can simply implement an identical selector of the superclass without any kind of warning.
> 
> Clearly this is problematic. What happens when a subclass does not call super, but should? I think the only answer is "undefined behavior". Typically, the (not so great) solution here is documentation. This is common in Cocoa and CocoaTouch, for example each time you (never) read the docs, you'll see things like "your implementation must call super at some point".
> 
> Swift improves on these deficiencies in the following ways:
> 
> (1) For public methods (or internal for classes in the same module), Swift requires the 'override' keyword. This is great. Clients are now aware that they are overriding a superclass method. Though this *should* be an indication to the client that super might need to be called and the docs should be read (lol), clients are not required to do either.
> 
> (2) Superclasses can prevent overriding using the 'final' keyword. This provides a lot of safety but results in an "all or nothing" decision for library authors.
> 
> (3) For initializers only, Swift enforces the 'override' keyword *and* a call to super due to Swift’s strict initialization rules. Thus, client subclasses can safely override initializers. Woo!
> 
> (4) Additionally, for initializers only, Swift superclasses can require that subclasses implement a specific initializer using the 'required' keyword (given that a subclass provides a custom init). Again, augmenting the subclass initialization flow can be done safely. ::applause::
> 
> OK. Hopefully I didn’t miss anything there. As you can see, there’s one major deficiency remaining in Swift:
> 
> - If a subclass chooses to override a non-final method of its superclass, there is no mechanism to require a call to super.
> 
> My proposed solution is straight-forward: extend the use of 'required' to methods, rather than only allow this for init.
> 
> Behavior would be the following:
> 
> - A superclass can specify a method as 'required'.
> - *If* a client’s subclass chooses to override a 'required' method, it must call super.
> - Without the 'required' keyword, current behavior would remain the same.

Thanks for bringing up this topic.  I agree that we need to support more expressive semantics for overrides.  I would take a slightly different approach though.

Your use of `required` has a different semantic than its use for initializers.  The change is arguably subtly, but it is meaningful.  I don’t think it is a good idea for it to have different semantics in different contexts.

For initializers `required` means “subclasses must provide an initializer with this signature.  The fact that you have to call super just falls out of that because it is an initializer.  You don’t actually have to call designated initializer that has the matching signature.

You are suggesting making `required` mean “you must call the exact same method on super”.  This would be unnecessarily limiting.

There are 3 possible semantics for overridability:

1) final / not overridable
2) overridable
3) required

I suggest expanding the use of `required` to mean #3 in general.  I would also suggest changing the default to #1.  That would require developers to think about subclasses before allowing overrides and also decide whether #2 or #3 is the correct semantics when overriding is allowed (I know this is controversial and somewhat orthogonal to your idea).  This would prompt the developer to think about the requirements to call super, which I discuss next.

There are also 4 possible semantics for "super requirements” when you do override:

1) no call to super required
2) requires super (must call super *if overriden*)
3) requires super first
4) requires super last

NS_REQUIRES_SUPER is equivalent to #2.

The last two are somewhat subtle, but it is occasionally reasonable for a superclass to want its implementation to run before or after a subclass implementation.  In both of these cases I would advocate for the compiler to automatically synthesize the call to super (if Swift supports these semantics) rather than requiring the developer to write the call.  The annotation specifies the behavior and the manual call would be redundant.

Swift doesn’t currently have any rules around calling super so we have #1 as a de-facto default with no way to change that.  That is probably ok as a default, especially if we require developers to think about subclasses when writing an overridable method (by making final the default).  If we introduce syntax to specify 2-4 we would have all cases covered.

Any of the “super requirements” could be specified in conjunction with either `overridable` or `required`.  This allows more expressiveness than mixing the two concepts together would support.

At minimum, I suggest that you use `required` as mentioned above and introduce new syntax to mean “must call the super implementation of the same method”.  Adding “super first” and “super last” would be nice as well, but can also be added later if you’re not interested in those semantics.

Matthew

> 
> Comments from Joe via twitter:
> - "Should be straight forward to implement."
> - "One interesting thing it would enable is invariant implementations of covariant protocol requirements." <dogScience.png>
> 
> Thanks for reading!
> Jesse
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution



More information about the swift-evolution mailing list