Scala extends Java’s basic class system, using the same notions for class, 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 issealed, 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
openmodifier.openis a soft modifier, like in Kotlin. -
abstractimpliesopen. - 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 ofCimplement 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
openmodifier. -
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 afinalorsealedmodifier out of laziiness.
Why is the default sealed and not final?
Writing simple class hierarchies is common. E.g.
class A
class B extends A
class C extends A
Requiring 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, B and C will be classified as final anyway since they are sealed and do not have any subclasses in the same compilation unit.
Is 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.