Hi Scala Community!
This thread is the SIP Committee’s request for comments on a proposal to introduce open class
es in the language. An open class is explicitly designed to be extended by subclasses, with the ability to override non-final methods with a clear contract. Classes that are neither open
, sealed
nor final
will by default be soft-sealed. Trying to extend a soft-sealed (normal) class from a different compilation will result in a warning, which can be silenced with a language import. You can find all the details here.
Motivation
For motivation, please read Open Classes.
Summary
When declaring a class in some file A.scala
, we can mark it with the open
modifier to communicate that it has been planned for extension:
open class A {
def someMethod(): Int = 42
def someOtherMethod(x: Int): Int = x + someMethod()
}
We can extend such a class from anywhere. An open class would typically come with a precise extension contract that describes internal calling patterns between the methods of the class. This is different from (and typically more complicated than) the external contract of a class which specifies how a class behaves when it is used. For example, the external contract of A
would specify that someOtherMethod(x)
returns x + 42
, but the extension contract would have to specify that it returns x + someMethod()
. The latter is required to make sense of what happens when someMethod()
is overridden by a subclass. Once that contract is established, it is no longer valid to refactor the implementation of someOtherMethod()
to be x + 42
.
If we try to extend a non-open class from a different source file, a new compilation warning will be emitted:
-- Feature Warning: EncryptedWriter.scala:6:14 ----
|class EncryptedWriter[T: Encryptable] extends Writer[T]
| ^
|Unless class Writer is declared 'open', its extension in a separate file should be enabled
|by adding the import clause 'import scala.language.adhocExtensions'
|or by setting the compiler option -language:adhocExtensions.
As an escape hatch—for example if you want to patch up a class in a library by overriding one its methods—, you can silence that warning with the following language import:
import scala.language.adhocExtensions
Alternatives
During the initial discussion by the SIP committee on this feature, a few alternatives have been mentioned:
- Make non-open classes eligible for extension in the enclosing package or even project, without requiring the language import. The notion of project is not well-defined in Scala, so only talking about the enclosing package is realistic.
- Turn the problem around: do not emit a warning a extension site, but instead emit a warning at definition site if we declare a class that is neither
final
norsealed
noropen
. This would be opt-in with a compiler option (a linter flag, really) which would typically be used by all libraries. @odersky objects that even beginners with the language should put careful consideration into declaring a class asopen
.
Discussion
A previous discussion on the topic can be found at
In that discussion, the main objections were:
- Users of libraries who want to “patch up” some bugs by extending library classes that were not carefully designed for their use case, and override some method. This is still possible with this proposal, simply by using the appropriate language import. Such patches are hacks that precisely counteract the fact that the library was not designed “well enough”; it makes sense for such hacks to require a language import.
- Test suites that use a lot of mocking, where the project’s classes are automatically extended by macros. This is addressed by using the command-line switch corresponding to the language import (
-language:adhocExtensions
). It would also be addressed by the alternative where a non-open class can be extended in the entire package, not just the same file.
Opening this Proposal for discussion by the community to get a wider perspective.