An Alternative for Extensibility Modifiers


(Jon Hull) #1

With all the controversy around proposal 0117, I thought I would take a stab at an alternative approach that attempts to balance the concerns of both sides of the argument. I don’t know that it would be everyone's first choice, but I think/hope it would be acceptable to the vast majority.

Take a look here and let me know if you think it is worth flushing out:
https://gist.github.com/jonhull/a5ac84a16b7a0ffc0c00747998f390d9

The basic idea (for those who hate clicking links) is to create 3 levels of extensibility:
  • Open - The class or method is open to be extended through subclassing/override.
  • Sealed - Attempting to subclass/override the class/method will result in a compiler error saying that it is not intended to be extended. Using the ‘unsafe' keyword will allow compilation.
  • Final - The class or method can not be subclassed/overridden. Attempting to do so will result in a compiler error.

These would be orthogonal to access modifiers. Thus you would write ‘public open’, which is admittedly two words… but together they are actually shorter than the single ‘subclassable’ keyword

You can also specify different levels of extensibility at different levels of visibility (e.g. ‘public final internal(open)’)

The default would be ‘sealed internal(open)’ which means it is publicly sealed, but open within the defining module. This is similar to the default of 0117, except it allows overriding using the ‘unsafe’ keyword (where the user explicitly acknowledges that subclassing/overriding is not supported by the API).

Thus, we lose some compiler optimizations in the default case (vs 0117), but these can easily be regained by explicitly marking parts of the API as final. The semantic meaning of the 0117 default is kept, with an escape hatch for problematic API. Thoughtful framework writers would go through and mark API as either ‘public open’ or ‘public final’. Less thoughtful framework authors would still get feedback asking them to do so, since nobody likes having the word ‘unsafe’ in their code when they don’t have to.

This design feels much Swiftier™ to me because it has opt-out safety and opt-in risk. It protects you from accidentally causing trouble, but gets out of your way when you tell it you know what you are doing.

Thoughts?

Thanks,
Jon


(Karoy Lorentey) #2

With all the controversy around proposal 0117, I thought I would take a stab at an alternative approach that attempts to balance the concerns of both sides of the argument. I don’t know that it would be everyone's first choice, but I think/hope it would be acceptable to the vast majority.

Take a look here and let me know if you think it is worth flushing out:
https://gist.github.com/jonhull/a5ac84a16b7a0ffc0c00747998f390d9

The basic idea (for those who hate clicking links) is to create 3 levels of extensibility:
  • Open - The class or method is open to be extended through subclassing/override.
  • Sealed - Attempting to subclass/override the class/method will result in a compiler error saying that it is not intended to be extended. Using the ‘unsafe' keyword will allow compilation.
  • Final - The class or method can not be subclassed/overridden. Attempting to do so will result in a compiler error.

These would be orthogonal to access modifiers. Thus you would write ‘public open’, which is admittedly two words… but together they are actually shorter than the single ‘subclassable’ keyword

You can also specify different levels of extensibility at different levels of visibility (e.g. ‘public final internal(open)’)

The default would be ‘sealed internal(open)’ which means it is publicly sealed, but open within the defining module. This is similar to the default of 0117, except it allows overriding using the ‘unsafe’ keyword (where the user explicitly acknowledges that subclassing/overriding is not supported by the API).

It seems to me adding such an escape hatch makes the entire point of the proposal moot.

If I say a class I define is internal or private, you have no business poking at it from an external module; if I say my class is sealed or final, you have no business subclassing it from an external module. Swift provides no escape hatch that exposes internal components of a module; I don't see why subclassibility requirements should be treated otherwise.

Note that most popular OOP languages provide well-known patterns for creating sealed but public classes, where only the author is able to create subclasses. These patterns typically involve hiding class constructors from external modules. If Swift was changed to support sealed classes the same way, would you then propose a language feature to defeat access modifiers?

···

On 2016-07-12 09:12:36 +0000, Jonathan Hull via swift-evolution said:

Thus, we lose some compiler optimizations in the default case (vs 0117), but these can easily be regained by explicitly marking parts of the API as final. The semantic meaning of the 0117 default is kept, with an escape hatch for problematic API. Thoughtful framework writers would go through and mark API as either ‘public open’ or ‘public final’. Less thoughtful framework authors would still get feedback asking them to do so, since nobody likes having the word ‘unsafe’ in their code when they don’t have to.

This design feels much Swiftier™ to me because it has opt-out safety and opt-in risk. It protects you from accidentally causing trouble, but gets out of your way when you tell it you know what you are doing.

--
Károly
@lorentey


(Charlie Monroe) #3

With all the controversy around proposal 0117, I thought I would take a stab at an alternative approach that attempts to balance the concerns of both sides of the argument. I don’t know that it would be everyone's first choice, but I think/hope it would be acceptable to the vast majority.

Take a look here and let me know if you think it is worth flushing out:
https://gist.github.com/jonhull/a5ac84a16b7a0ffc0c00747998f390d9

The basic idea (for those who hate clicking links) is to create 3 levels of extensibility:
  • Open - The class or method is open to be extended through subclassing/override.
  • Sealed - Attempting to subclass/override the class/method will result in a compiler error saying that it is not intended to be extended. Using the ‘unsafe' keyword will allow compilation.
  • Final - The class or method can not be subclassed/overridden. Attempting to do so will result in a compiler error.

These would be orthogonal to access modifiers. Thus you would write ‘public open’, which is admittedly two words… but together they are actually shorter than the single ‘subclassable’ keyword

You can also specify different levels of extensibility at different levels of visibility (e.g. ‘public final internal(open)’)

The default would be ‘sealed internal(open)’ which means it is publicly sealed, but open within the defining module. This is similar to the default of 0117, except it allows overriding using the ‘unsafe’ keyword (where the user explicitly acknowledges that subclassing/overriding is not supported by the API).

This not only looses some of the whole-module optimization benefits but also renders many other future proposals moot. For example the exhaustive switch based on a sealed class:

let x: MySealedClass = ...
switch x {
case let obj as X:
  ...
case let obj as Y:
  ...
}

where X and Y are subclasses of MySealedClass. The benefit of this is no "default" case and a compile-time check that all the cases are handled, just like with an enum (since the class is sealed, the compiler knows all possible cases). You can probably imagine how would this get broken when if you used "unsafe" subclassing.

Sure, there could be an implicit default: fatalError("You've subclassed what should never have been subclassed."), but it just doesn't feel right.

···

On Jul 12, 2016, at 11:12 AM, Jonathan Hull via swift-evolution <swift-evolution@swift.org> wrote:

Thus, we lose some compiler optimizations in the default case (vs 0117), but these can easily be regained by explicitly marking parts of the API as final. The semantic meaning of the 0117 default is kept, with an escape hatch for problematic API. Thoughtful framework writers would go through and mark API as either ‘public open’ or ‘public final’. Less thoughtful framework authors would still get feedback asking them to do so, since nobody likes having the word ‘unsafe’ in their code when they don’t have to.

This design feels much Swiftier™ to me because it has opt-out safety and opt-in risk. It protects you from accidentally causing trouble, but gets out of your way when you tell it you know what you are doing.

Thoughts?

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


(Tino) #4

Imho overall this is a good proposal, yet it saddens me to read this thread, because it's a good proof for the dogmatism I mentioned before:
You address all issues of proposal 0117 in a more consistent way with less confusing keywords, even adding some features that are actually handy — but yet the tenor of the replies is pushback…

As a liberal, those discussions are a tough battle, because you accept other people's opinions and strive for compromise, while the other side keeps pushing their case as hard as possible to enforce their agenda:
- We need to have "sealed"!
- It has to be the default!
- There has to be no way out of it!

To add some more constructive thoughts:
Given the fact that we are talking about changing a situation where it's absolutely legitimate to override (it is the way Swift has been designed!), I really thing there is no need for an extra "unsafe"-keyword — a simple exclamation-mark would do.

Tino


(Jon Hull) #5

You get all of these back by declaring things ‘final’. Final does not have the escape hatch. The escape hatch is only for ‘sealed’. Make sure you actually read the proposal I made… the terminology is slightly different than 0117.

Thanks,
Jon

···

On Jul 12, 2016, at 4:20 AM, Charlie Monroe <charlie@charliemonroe.net> wrote:

On Jul 12, 2016, at 11:12 AM, Jonathan Hull via swift-evolution <swift-evolution@swift.org> wrote:

With all the controversy around proposal 0117, I thought I would take a stab at an alternative approach that attempts to balance the concerns of both sides of the argument. I don’t know that it would be everyone's first choice, but I think/hope it would be acceptable to the vast majority.

Take a look here and let me know if you think it is worth flushing out:
https://gist.github.com/jonhull/a5ac84a16b7a0ffc0c00747998f390d9

The basic idea (for those who hate clicking links) is to create 3 levels of extensibility:
  • Open - The class or method is open to be extended through subclassing/override.
  • Sealed - Attempting to subclass/override the class/method will result in a compiler error saying that it is not intended to be extended. Using the ‘unsafe' keyword will allow compilation.
  • Final - The class or method can not be subclassed/overridden. Attempting to do so will result in a compiler error.

These would be orthogonal to access modifiers. Thus you would write ‘public open’, which is admittedly two words… but together they are actually shorter than the single ‘subclassable’ keyword

You can also specify different levels of extensibility at different levels of visibility (e.g. ‘public final internal(open)’)

The default would be ‘sealed internal(open)’ which means it is publicly sealed, but open within the defining module. This is similar to the default of 0117, except it allows overriding using the ‘unsafe’ keyword (where the user explicitly acknowledges that subclassing/overriding is not supported by the API).

This not only looses some of the whole-module optimization benefits but also renders many other future proposals moot. For example the exhaustive switch based on a sealed class:

let x: MySealedClass = ...
switch x {
case let obj as X:
  ...
case let obj as Y:
  ...
}

where X and Y are subclasses of MySealedClass. The benefit of this is no "default" case and a compile-time check that all the cases are handled, just like with an enum (since the class is sealed, the compiler knows all possible cases). You can probably imagine how would this get broken when if you used "unsafe" subclassing.

Sure, there could be an implicit default: fatalError("You've subclassed what should never have been subclassed."), but it just doesn't feel right.


(James Campbell) #6

I think sealing classes already existi in Swift it's called Private :slight_smile:

···

*___________________________________*

*James⎥Head of Trolls*

*james@supmenow.com <james@supmenow.com>⎥supmenow.com <http://supmenow.com>*

*Sup*

*Runway East *

*10 Finsbury Square*

*London*

* EC2A 1AF *

On 12 July 2016 at 12:20, Charlie Monroe via swift-evolution < swift-evolution@swift.org> wrote:

> On Jul 12, 2016, at 11:12 AM, Jonathan Hull via swift-evolution < > swift-evolution@swift.org> wrote:
>
> With all the controversy around proposal 0117, I thought I would take a
stab at an alternative approach that attempts to balance the concerns of
both sides of the argument. I don’t know that it would be everyone's first
choice, but I think/hope it would be acceptable to the vast majority.
>
> Take a look here and let me know if you think it is worth flushing out:
> https://gist.github.com/jonhull/a5ac84a16b7a0ffc0c00747998f390d9
>
>
> The basic idea (for those who hate clicking links) is to create 3 levels
of extensibility:
> • Open - The class or method is open to be extended through
subclassing/override.
> • Sealed - Attempting to subclass/override the class/method will
result in a compiler error saying that it is not intended to be extended.
Using the ‘unsafe' keyword will allow compilation.
> • Final - The class or method can not be subclassed/overridden.
Attempting to do so will result in a compiler error.
>
> These would be orthogonal to access modifiers. Thus you would write
‘public open’, which is admittedly two words… but together they are
actually shorter than the single ‘subclassable’ keyword
>
> You can also specify different levels of extensibility at different
levels of visibility (e.g. ‘public final internal(open)’)
>
> The default would be ‘sealed internal(open)’ which means it is publicly
sealed, but open within the defining module. This is similar to the
default of 0117, except it allows overriding using the ‘unsafe’ keyword
(where the user explicitly acknowledges that subclassing/overriding is not
supported by the API).

This not only looses some of the whole-module optimization benefits but
also renders many other future proposals moot. For example the exhaustive
switch based on a sealed class:

let x: MySealedClass = ...
switch x {
case let obj as X:
        ...
case let obj as Y:
        ...
}

where X and Y are subclasses of MySealedClass. The benefit of this is no
"default" case and a compile-time check that all the cases are handled,
just like with an enum (since the class is sealed, the compiler knows all
possible cases). You can probably imagine how would this get broken when if
you used "unsafe" subclassing.

Sure, there could be an implicit default: fatalError("You've subclassed
what should never have been subclassed."), but it just doesn't feel right.

>
> Thus, we lose some compiler optimizations in the default case (vs 0117),
but these can easily be regained by explicitly marking parts of the API as
final. The semantic meaning of the 0117 default is kept, with an escape
hatch for problematic API. Thoughtful framework writers would go through
and mark API as either ‘public open’ or ‘public final’. Less thoughtful
framework authors would still get feedback asking them to do so, since
nobody likes having the word ‘unsafe’ in their code when they don’t have to.
>
> This design feels much Swiftier™ to me because it has opt-out safety and
opt-in risk. It protects you from accidentally causing trouble, but gets
out of your way when you tell it you know what you are doing.
>
> Thoughts?
>
> Thanks,
> Jon
> _______________________________________________
> 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