Scala extends Java’s basic class system, using the same notions for
abstract class and
final class. In hindsight, I believe the default in this system that every class is extensible is problematic. It leads to straightforward code being vulnerable to unforeseen overrides and requires
final modifiers in too many places.
This pre SIP is a proposal to change the default class extensibility to
sealed. It can be summarized as follows:
- A normal
classdefinition implies that the class is
sealed, I.e. if it has subclasses, they must be in the the same compilation unit.
- To have a class that can be extended in other compilation units, the class must have the
openis a soft modifier, like in Kotlin.
- A sealed, non-abstract class
C(i.e. the default) is allowed to have abstract members. It is then checked that all non-abstract direct subclasses of
Cimplement these members. This is easy, as all these classes are in the same compilation unit.
Why Propose This Now?
Scala 3.0 is already over-full with changes. Can this not wait for a future version?
The problem with waiting is that we want to roll changes that “require the books to be rewritten” all into 3.0, so that docs stay valid for a long time afterwards. Changing the default for class extensibility is so fundamental that it would affect every tutorial.
How To Get There?
Since this is a fundamental change, changing existing code bases will be a hassle. Some measures to soften the impact are:
The Scala 3 compiler will treat all Java classes and all Scala 2.x compiled classes as
open. So they can be freely extended in application code.
Extending a default class in another compilation unit will give a deprecation warning. We will wait to see how migration goes before turning that into a hard error. EDIT: Or maybe use some other warning instead of a hard error. The warnings should be turned on only in Scala 3.1. That way, we still enable cross compilation between Scala 3.0 and Scala 2, which does not allow the
We should think whether some automatic rewrites are possible. This is not trivial however, since simply slapping
openon every class would probably be wrong in more cases than it is right. I believe that most classes are in fact not intended to be open and just did not get a
sealedmodifier out of laziiness.
Why is the default
sealed and not
Writing simple class hierarchies is common. E.g.
class A class B extends A class C extends A
sealed in front of
class A means the information that’s already present in the
extends clauses has to be specified again, which is unnecessarily pedantic. Indeed,
C will be classified as
final anyway since they are sealed and do not have any subclasses in the same compilation unit.
sealed still needed as a modifier?
Not really, The only remaining use case is
sealed trait. But if we talk about a single parent of a finite number of alternatives, making the parent a (sealed by default)
class is more appropriate. Otherwise, we’d be looking at a set of classes that have different parent classes, yet all implement an additional trait, which furthermore is restricted to be extended by just these classes. I believe that use case is very uncommon. So, in the interest of not offering unnecessary choices, we should deprecate
sealed in a future version of the language.
EDIT: it looks like
sealed is still needed for traits. An example is an ADT for an abstract syntax tree, where all cases extend one base class, but certain cases extend some additional classification trait. The trait should be
sealed to give pattern matching exhaustivity.