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
class
definition 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
open
modifier.open
is a soft modifier, like in Kotlin. -
abstract
impliesopen
. - 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 ofC
implement 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
open
modifier. -
We should think whether some automatic rewrites are possible. This is not trivial however, since simply slapping
open
on 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 afinal
orsealed
modifier 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.