I love sealed traits and utilizing the compiler exhaustive check in pattern matching.
What I don’t like is that I’m limited to extending the sealed traits in the same source file.
I propose a compromise that we can optionally annotate the extending classes within the extended trait call site and then the extending classes can exist in other source files. We can desugar this concept into regular sealed traits with an auxiliary union type.
Here is an example:
Before desugaring
Foo.scala
sealed[Bar, Baz.type] trait Foo
case class OtherFoo() extends Foo
Bar.scala
case class Bar() extends Foo
Baz.scala
case object Baz extends Foo
Test.scala
def check(f: Foo): Unit =
f match //match may not be exhaustive.
case OtherFoo() =>
case Baz =>
After desugaring
Foo.scala
type Foo$Sealed = OtherFoo | Bar | Baz.type
trait Foo
case class OtherFoo() extends Foo
Bar.scala
case class Bar() extends Foo
Baz.scala
case object Baz extends Foo
Test.scala
def check(f: Foo$Sealed): Unit =
f match //match may not be exhaustive.
case OtherFoo() =>
case Baz =>
Desugraing concept
The sealed trait/classed with annotated extenders is changed to the regular trait/class without the sealed modifier:
`sealed[A,B,C...] trait Foo`
is changed to
trait Foo
A new auxiliary type is added that is a union of all extenders:
type Foo$Sealed = A | B | C | ... | <Additional extenders that are in the same source file as Foo>
All extending classes and pattern matching references continue to reference the original Foo trait
All other references to Foo are changed to Foo$Sealed
I can’t say anything about whether the suggested implementation makes sense. But I would love to have sealed traits that can span multiple files. I am not entirely sure about the syntax though since it is yet another way of using the square brackets (on top types, scope).
I actually think the Java sealed interface syntax with permits is really clear.
AFAIK, it is not realistic to have sealed types that implicitly span across multiple source files, hence my suggestion to add explicit annotation of what is extending. The desugaring concept is to allow the majority of the compiler to remain unchanged. It may be possible to forfeit the desugaring and modify the wait sealed is supported throughout the compiler. But the annotation must remain, although we can think of a different syntax for it.
I don’t see why we would need this $Sealed union type. It should be perfectly possible to directly implement multi file sealed traits and classes just like the single file ones. The single file restriction is necessary to identify what are the possible subclasses. If they’re listed in the class header, that works too.