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


(Jesse Squires) #1

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 :slight_smile: 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.

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


(Matthew Johnson) #2

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 :slight_smile: 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

···

On Jan 17, 2016, at 5:54 PM, Jesse Squires via swift-evolution <swift-evolution@swift.org> wrote:

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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Will Entriken) #3

Matthew,

This does not address the issue that a class may require all its
subclasses' initializers to call one of its initializers, but does not care
which one.

Are there other languages worth investigating which have the requires super
first/last semantics you note?

···

On Mon, Jan 18, 2016 at 10:19 AM, Matthew Johnson via swift-evolution < swift-evolution@swift.org> wrote:

> On Jan 17, 2016, at 5:54 PM, Jesse Squires via swift-evolution < > swift-evolution@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 :slight_smile: 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@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

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


(Matthew Johnson) #4

Matthew,

This does not address the issue that a class may require all its subclasses' initializers to call one of its initializers, but does not care which one.

This is required by Swift’s definitive initialization rules and applies to *all* initializers. It isn’t really relevant to overriding in general.

Are there other languages worth investigating which have the requires super first/last semantics you note?

I don’t know of any languages that support this. It is not the most common semantic, but when it occurs it seems important enough that it would be better if it could be specified in the language itself and enforced rather than left to documentation.

Here are some examples from CocoaTouch:

You can subclass UIPercentDrivenInteractiveTransition, but if you do so you must start each of your method overrides with a call to the super implementation of the method.

// call super first and return that result if provided
UIViewController.transitionCoordinator() -> UIViewControllerTransitionCoordinator?

// call super first to retrieve the item’s existing attributes and then make your changes to the returned structure.
layoutAttributesForInteractivelyMovingItemAtIndexPath(_ indexPath: NSIndexPath, withTargetPosition position: CGPoint) -> UICollectionViewLayoutAttributes

// If you override this method, you must call super first to get the invalidation context object to return. After getting this object, set any custom properties and return it.
func invalidationContextForBoundsChange(_ newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext

// call super last
UIView.updateConstraints()

When I suggested having the compiler synthesize the calls in for first / last I was thinking of cases with void return. Several of these examples don’t have void return and the result is used in different ways. I no longer think the compiler should attempt to synthesize anything, but it should be able to enforce the requirement that we manually write a super call in the correct location.

One interesting thing is that the exercise of looking for examples also turned up another semantic, cannot call super:

UIViewController. loadView()
UIPresentationController.shouldPresentInFullscreen() -> Bool
UIPresentationController.shouldRemovePresentersView() -> Bool

That leaves us with 5 possible super semantics for overrides:

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

These exist in practice. The question is whether the language should allow them to be stated directly and enforce them or not. I believe it should. I don’t trust documentation to be complete or to be read carefully by everyone who should read it. Adding language support makes it clear that the proper semantics for calling super should be considered carefully and stated explicitly. In return the compiler ensures subclasses are well-behaved.

-Matthew

···

On Jan 18, 2016, at 10:08 AM, Will Entriken <fulldecent@gmail.com> wrote:

On Mon, Jan 18, 2016 at 10:19 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

> On Jan 17, 2016, at 5:54 PM, Jesse Squires via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@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 :slight_smile: 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@swift.org <mailto:swift-evolution@swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution

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


(Howard Lovatt) #5

Are there other languages worth investigating which have the requires super first/last semantics you note?

I don’t know of any languages that support this. It is not the most common semantic, but when it occurs it seems important enough that it would be better if it could be specified in the language itself and enforced rather than left to documentation.

BETA has this ability, but it worked rather differently than modern OO languages (which work more like its predecessor Simula). In BETA you can only ever call the top most method, it can then optionally call the immediately overriding method. The syntax BETA uses is `inner(<args>)`. Probably easier with examples (in BETArised Swift):

    class Base {
        func finalM() { print("finalM") }
        func mustOverrideM() {
            print("beforeInner")
            inner()
            print("afterInner")
        }
    }
    
    class Derived: Base {
        func mustOverrideM() {
            print("inInner")
        }
    }

Methods `finalM` and `Derived.mustOverrideM` are automatically final because they do not call inner.

You can't make an instance of `Base` because method `Base.mustOverrideM` needs to be overridden, i.e. `Base` is automatically abstract.

If you:

    let d = Derived()
    d.mustBeOverriddenM()

Then it prints:

    beforeInner
    inInner
    afterInner


(Jesse Squires) #6

Thanks Matthew and Howard! A lot of good points there.

There are certainly a few more possible semantics for overrides than I was initially thinking. It’s not clear to me if the language should support each use case or not. Doing so seems to add unnecessary complexity for (potentially) little gain.

I can’t say for sure, but my guess is that the *most common* scenario is: "if a method is overridden, it must call super first". This would also make automatically synthesizing the call to super simpler, which I think is a great idea. Clients then have less of a burden when subclassing.
However, as noted, non-void returns complicate this. Also, I agree that re-purposing `required` could be confusing. I’m open to ideas for an alternative keyword.

Again, a lot of good ideas/feedback. I’m actually not sure where this leaves us. :slight_smile: Accounting for all of the possible semantics seems too complex and burdensome, though I think our current state is insufficient. And perhaps accounting for only the simplest scenario, "if a method is overridden, it must call super first", would only further highlight the lack of ability to express the other possible semantics.

¯\_(ツ)_/¯ I’d love to hear if anyone else has ideas or shares these concerns!

Jesse

···

On Jan 18, 2016, at 11:34 AM, Howard Lovatt via swift-evolution <swift-evolution@swift.org> wrote:

Are there other languages worth investigating which have the requires super first/last semantics you note?

I don’t know of any languages that support this. It is not the most common semantic, but when it occurs it seems important enough that it would be better if it could be specified in the language itself and enforced rather than left to documentation.

BETA has this ability, but it worked rather differently than modern OO languages (which work more like its predecessor Simula). In BETA you can only ever call the top most method, it can then optionally call the immediately overriding method. The syntax BETA uses is `inner(<args>)`. Probably easier with examples (in BETArised Swift):

   class Base {
       func finalM() { print("finalM") }
       func mustOverrideM() {
           print("beforeInner")
           inner()
           print("afterInner")
       }
   }

   class Derived: Base {
       func mustOverrideM() {
           print("inInner")
       }
   }

Methods `finalM` and `Derived.mustOverrideM` are automatically final because they do not call inner.

You can't make an instance of `Base` because method `Base.mustOverrideM` needs to be overridden, i.e. `Base` is automatically abstract.

If you:

   let d = Derived()
   d.mustBeOverriddenM()

Then it prints:

   beforeInner
   inInner
   afterInner

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


(Nate Birkholz) #7

I feel like leaving whether and when to call super up to the programmer is
a more general solution. In my current codebase, calling super on an
inherited method is what i *don't* want, instead overriding the whole
method on every child.

···

On Wed, Jan 20, 2016 at 4:53 PM, Jesse Squires via swift-evolution < swift-evolution@swift.org> wrote:

Thanks Matthew and Howard! A lot of good points there.

There are certainly a few more possible semantics for overrides than I was
initially thinking. It’s not clear to me if the language should support
each use case or not. Doing so seems to add unnecessary complexity for
(potentially) little gain.

I can’t say for sure, but my guess is that the *most common* scenario is:
"if a method is overridden, it must call super first". This would also make
automatically synthesizing the call to super simpler, which I think is a
great idea. Clients then have less of a burden when subclassing.
However, as noted, non-void returns complicate this. Also, I agree that
re-purposing `required` could be confusing. I’m open to ideas for an
alternative keyword.

Again, a lot of good ideas/feedback. I’m actually not sure where this
leaves us. :slight_smile: Accounting for all of the possible semantics seems too
complex and burdensome, though I think our current state is insufficient.
And perhaps accounting for only the simplest scenario, "if a method is
overridden, it must call super first", would only further highlight the
lack of ability to express the other possible semantics.

¯\_(ツ)_/¯ I’d love to hear if anyone else has ideas or shares these
concerns!

Jesse

> On Jan 18, 2016, at 11:34 AM, Howard Lovatt via swift-evolution < > swift-evolution@swift.org> wrote:
>
>>>
>>> Are there other languages worth investigating which have the requires
super first/last semantics you note?
>>
>> I don’t know of any languages that support this. It is not the most
common semantic, but when it occurs it seems important enough that it would
be better if it could be specified in the language itself and enforced
rather than left to documentation.
>
> BETA has this ability, but it worked rather differently than modern OO
languages (which work more like its predecessor Simula). In BETA you can
only ever call the top most method, it can then optionally call the
immediately overriding method. The syntax BETA uses is `inner(<args>)`.
Probably easier with examples (in BETArised Swift):
>
> class Base {
> func finalM() { print("finalM") }
> func mustOverrideM() {
> print("beforeInner")
> inner()
> print("afterInner")
> }
> }
>
> class Derived: Base {
> func mustOverrideM() {
> print("inInner")
> }
> }
>
> Methods `finalM` and `Derived.mustOverrideM` are automatically final
because they do not call inner.
>
> You can't make an instance of `Base` because method `Base.mustOverrideM`
needs to be overridden, i.e. `Base` is automatically abstract.
>
> If you:
>
> let d = Derived()
> d.mustBeOverriddenM()
>
> Then it prints:
>
> beforeInner
> inInner
> afterInner
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

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

--
Nate Birkholz


(Matthew Johnson) #8

Thanks Matthew and Howard! A lot of good points there.

There are certainly a few more possible semantics for overrides than I was initially thinking. It’s not clear to me if the language should support each use case or not. Doing so seems to add unnecessary complexity for (potentially) little gain.

I can’t say for sure, but my guess is that the *most common* scenario is: "if a method is overridden, it must call super first". This would also make automatically synthesizing the call to super simpler, which I think is a great idea. Clients then have less of a burden when subclassing.
However, as noted, non-void returns complicate this. Also, I agree that re-purposing `required` could be confusing. I’m open to ideas for an alternative keyword.

Again, a lot of good ideas/feedback. I’m actually not sure where this leaves us. :slight_smile: Accounting for all of the possible semantics seems too complex and burdensome, though I think our current state is insufficient. And perhaps accounting for only the simplest scenario, "if a method is overridden, it must call super first", would only further highlight the lack of ability to express the other possible semantics.

¯\_(ツ)_/¯ I’d love to hear if anyone else has ideas or shares these concerns!

Are all of the possible semantics a bit complex and perhaps subtle in some cases? You can argue that. However, they do exist *in practice* whether they can be expressed in the language or not. We currently rely on documentation to communicate the semantics that subclasses must adhere to in order to function properly.

Personally, I consider it a flaw in the language that we must rely on documentation for this kind of thing if it is considered an acceptable requirement to place on subclasses. It would be much better to express directly and have it enforced by the compiler. I don’t believe it would be correct to consider this “complex and burdensome” in the language. The complexity is inherent in the design of the superclass. The question is how that complexity is communicated to developers and whether or not mistakes are caught at compile time.

At the same time, I understand the desire to keep the language simple. Assuming that is the direction you want to go my recommendation is:

1. Allow `required` to be used on any method, indicating that all subclasses must implement the method (matching its semantics on initializers).
2. Add a new annotation indicating that overrides must call super (maybe @requires_super, but could be something else).

We can always add “first” and “last” parameters on that annotation - @requires_super(first) and @requires_super(last) - as well as an annotation along the lines of @no_super down the road.

-Matthew

···

On Jan 20, 2016, at 6:53 PM, Jesse Squires <jesse.d.squires@gmail.com> wrote:

Jesse

On Jan 18, 2016, at 11:34 AM, Howard Lovatt via swift-evolution <swift-evolution@swift.org> wrote:

Are there other languages worth investigating which have the requires super first/last semantics you note?

I don’t know of any languages that support this. It is not the most common semantic, but when it occurs it seems important enough that it would be better if it could be specified in the language itself and enforced rather than left to documentation.

BETA has this ability, but it worked rather differently than modern OO languages (which work more like its predecessor Simula). In BETA you can only ever call the top most method, it can then optionally call the immediately overriding method. The syntax BETA uses is `inner(<args>)`. Probably easier with examples (in BETArised Swift):

  class Base {
      func finalM() { print("finalM") }
      func mustOverrideM() {
          print("beforeInner")
          inner()
          print("afterInner")
      }
  }

  class Derived: Base {
      func mustOverrideM() {
          print("inInner")
      }
  }

Methods `finalM` and `Derived.mustOverrideM` are automatically final because they do not call inner.

You can't make an instance of `Base` because method `Base.mustOverrideM` needs to be overridden, i.e. `Base` is automatically abstract.

If you:

  let d = Derived()
  d.mustBeOverriddenM()

Then it prints:

  beforeInner
  inInner
  afterInner

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


(Howard Lovatt) #9

Adding protected access and abstract methods and classes might be easier,
since then you could do:

/* abstract */ class Base {

    /* protected abstract */ func methodToBeOverridden() -> String {
fatalError("Abstract") }

    final func controlsWhenOverrideIsCalled() -> String{

        var s = "In 'super' before override call.\n"

        s += methodToBeOverridden()

        return s + ".\nIn 'super' after override call."

    }

}

class Derived: Base {

    /* protected */ override func methodToBeOverridden() -> String {

        return "In override"

    }

}

let d = Derived()

d.controlsWhenOverrideIsCalled()

Which results in:

In 'super' before override call.

In override.

In 'super' after override call.

Calls to super could then be eliminated from the language!

···

On 21 January 2016 at 11:53, Jesse Squires <jesse.d.squires@gmail.com> wrote:

Thanks Matthew and Howard! A lot of good points there.

There are certainly a few more possible semantics for overrides than I was
initially thinking. It’s not clear to me if the language should support
each use case or not. Doing so seems to add unnecessary complexity for
(potentially) little gain.

I can’t say for sure, but my guess is that the *most common* scenario is:
"if a method is overridden, it must call super first". This would also make
automatically synthesizing the call to super simpler, which I think is a
great idea. Clients then have less of a burden when subclassing.
However, as noted, non-void returns complicate this. Also, I agree that
re-purposing `required` could be confusing. I’m open to ideas for an
alternative keyword.

Again, a lot of good ideas/feedback. I’m actually not sure where this
leaves us. :slight_smile: Accounting for all of the possible semantics seems too
complex and burdensome, though I think our current state is insufficient.
And perhaps accounting for only the simplest scenario, "if a method is
overridden, it must call super first", would only further highlight the
lack of ability to express the other possible semantics.

¯\_(ツ)_/¯ I’d love to hear if anyone else has ideas or shares these
concerns!

Jesse

> On Jan 18, 2016, at 11:34 AM, Howard Lovatt via swift-evolution < > swift-evolution@swift.org> wrote:
>
>>>
>>> Are there other languages worth investigating which have the requires
super first/last semantics you note?
>>
>> I don’t know of any languages that support this. It is not the most
common semantic, but when it occurs it seems important enough that it would
be better if it could be specified in the language itself and enforced
rather than left to documentation.
>
> BETA has this ability, but it worked rather differently than modern OO
languages (which work more like its predecessor Simula). In BETA you can
only ever call the top most method, it can then optionally call the
immediately overriding method. The syntax BETA uses is `inner(<args>)`.
Probably easier with examples (in BETArised Swift):
>
> class Base {
> func finalM() { print("finalM") }
> func mustOverrideM() {
> print("beforeInner")
> inner()
> print("afterInner")
> }
> }
>
> class Derived: Base {
> func mustOverrideM() {
> print("inInner")
> }
> }
>
> Methods `finalM` and `Derived.mustOverrideM` are automatically final
because they do not call inner.
>
> You can't make an instance of `Base` because method `Base.mustOverrideM`
needs to be overridden, i.e. `Base` is automatically abstract.
>
> If you:
>
> let d = Derived()
> d.mustBeOverriddenM()
>
> Then it prints:
>
> beforeInner
> inInner
> afterInner
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

--
  -- Howard.


(Matthew Johnson) #10

I feel like leaving whether and when to call super up to the programmer is a more general solution. In my current codebase, calling super on an inherited method is what i *don't* want, instead overriding the whole method on every child.

In cases where it doesn’t matter whether super is called or not that works fine. However, if the superclass is designed in such a way that certain methods must *always* be called if overrides it will lead to bugs when that doesn’t happen. In these cases it would be nice if the compiler could enforce that for us. In cases where you *don’t* want super to be called you would use an annotation like @no_super to make sure that it *isn’t* called by subclasses.

This comes down to whether we believe expressing and enforcing the intended override semantics in the language is worthwhile or not. I believe it is.

-Matthew

···

On Jan 20, 2016, at 7:01 PM, Nate Birkholz via swift-evolution <swift-evolution@swift.org> wrote:

On Wed, Jan 20, 2016 at 4:53 PM, Jesse Squires via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Thanks Matthew and Howard! A lot of good points there.

There are certainly a few more possible semantics for overrides than I was initially thinking. It’s not clear to me if the language should support each use case or not. Doing so seems to add unnecessary complexity for (potentially) little gain.

I can’t say for sure, but my guess is that the *most common* scenario is: "if a method is overridden, it must call super first". This would also make automatically synthesizing the call to super simpler, which I think is a great idea. Clients then have less of a burden when subclassing.
However, as noted, non-void returns complicate this. Also, I agree that re-purposing `required` could be confusing. I’m open to ideas for an alternative keyword.

Again, a lot of good ideas/feedback. I’m actually not sure where this leaves us. :slight_smile: Accounting for all of the possible semantics seems too complex and burdensome, though I think our current state is insufficient. And perhaps accounting for only the simplest scenario, "if a method is overridden, it must call super first", would only further highlight the lack of ability to express the other possible semantics.

¯\_(ツ)_/¯ I’d love to hear if anyone else has ideas or shares these concerns!

Jesse

> On Jan 18, 2016, at 11:34 AM, Howard Lovatt via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>
>>>
>>> Are there other languages worth investigating which have the requires super first/last semantics you note?
>>
>> I don’t know of any languages that support this. It is not the most common semantic, but when it occurs it seems important enough that it would be better if it could be specified in the language itself and enforced rather than left to documentation.
>
> BETA has this ability, but it worked rather differently than modern OO languages (which work more like its predecessor Simula). In BETA you can only ever call the top most method, it can then optionally call the immediately overriding method. The syntax BETA uses is `inner(<args>)`. Probably easier with examples (in BETArised Swift):
>
> class Base {
> func finalM() { print("finalM") }
> func mustOverrideM() {
> print("beforeInner")
> inner()
> print("afterInner")
> }
> }
>
> class Derived: Base {
> func mustOverrideM() {
> print("inInner")
> }
> }
>
> Methods `finalM` and `Derived.mustOverrideM` are automatically final because they do not call inner.
>
> You can't make an instance of `Base` because method `Base.mustOverrideM` needs to be overridden, i.e. `Base` is automatically abstract.
>
> If you:
>
> let d = Derived()
> d.mustBeOverriddenM()
>
> Then it prints:
>
> beforeInner
> inInner
> afterInner
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution

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

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


(Matthew Johnson) #11

Adding protected access and abstract methods and classes might be easier, since then you could do:

/* abstract */ class Base {
    /* protected abstract */ func methodToBeOverridden() -> String { fatalError("Abstract") }
    final func controlsWhenOverrideIsCalled() -> String{
        var s = "In 'super' before override call.\n"
        s += methodToBeOverridden()
        return s + ".\nIn 'super' after override call."
    }
}

class Derived: Base {
    /* protected */ override func methodToBeOverridden() -> String {
        return "In override"
    }
}

let d = Derived()
d.controlsWhenOverrideIsCalled()

Which results in:

In 'super' before override call.
In override.
In 'super' after override call.

Calls to super could then be eliminated from the language!

Not really. This covers some use cases but by no means all of them. It also adds complexity to the design (by introducing two names).

Aside from that, we are not going to see `protected` access control in Swift. It does not fit the access control model of the language, which is scope-based rather than type-based. This is a good thing IMO.

-Matthew

···

On Jan 20, 2016, at 8:20 PM, Howard Lovatt <howard.lovatt@gmail.com> wrote:

On 21 January 2016 at 11:53, Jesse Squires <jesse.d.squires@gmail.com <mailto:jesse.d.squires@gmail.com>> wrote:
Thanks Matthew and Howard! A lot of good points there.

There are certainly a few more possible semantics for overrides than I was initially thinking. It’s not clear to me if the language should support each use case or not. Doing so seems to add unnecessary complexity for (potentially) little gain.

I can’t say for sure, but my guess is that the *most common* scenario is: "if a method is overridden, it must call super first". This would also make automatically synthesizing the call to super simpler, which I think is a great idea. Clients then have less of a burden when subclassing.
However, as noted, non-void returns complicate this. Also, I agree that re-purposing `required` could be confusing. I’m open to ideas for an alternative keyword.

Again, a lot of good ideas/feedback. I’m actually not sure where this leaves us. :slight_smile: Accounting for all of the possible semantics seems too complex and burdensome, though I think our current state is insufficient. And perhaps accounting for only the simplest scenario, "if a method is overridden, it must call super first", would only further highlight the lack of ability to express the other possible semantics.

¯\_(ツ)_/¯ I’d love to hear if anyone else has ideas or shares these concerns!

Jesse

> On Jan 18, 2016, at 11:34 AM, Howard Lovatt via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>
>>>
>>> Are there other languages worth investigating which have the requires super first/last semantics you note?
>>
>> I don’t know of any languages that support this. It is not the most common semantic, but when it occurs it seems important enough that it would be better if it could be specified in the language itself and enforced rather than left to documentation.
>
> BETA has this ability, but it worked rather differently than modern OO languages (which work more like its predecessor Simula). In BETA you can only ever call the top most method, it can then optionally call the immediately overriding method. The syntax BETA uses is `inner(<args>)`. Probably easier with examples (in BETArised Swift):
>
> class Base {
> func finalM() { print("finalM") }
> func mustOverrideM() {
> print("beforeInner")
> inner()
> print("afterInner")
> }
> }
>
> class Derived: Base {
> func mustOverrideM() {
> print("inInner")
> }
> }
>
> Methods `finalM` and `Derived.mustOverrideM` are automatically final because they do not call inner.
>
> You can't make an instance of `Base` because method `Base.mustOverrideM` needs to be overridden, i.e. `Base` is automatically abstract.
>
> If you:
>
> let d = Derived()
> d.mustBeOverriddenM()
>
> Then it prints:
>
> beforeInner
> inInner
> afterInner
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution

--
  -- Howard.