Illegal inheritance from sealed class Type

Is there a way to create an ADT which spans files?

I tried to create an sealed abstract class in one file, and extend it in another class, and I get the error

illegal inheritance from sealed class Type
object EmptyType extends Type {

Does this really mean that ALL the code for an ADT must go in one file, even if it is many 1000s of lines long?

No.

Yes, but actually no.

You can do something like this:

// All the following code will be on its own package (pckg) and thus in its own folder.

// pckg/adt.scala
package pckg

sealed trait MyADT extends Product with Serializable
final case class Foo(x: Int, y: Int) extends MyADT with FooImpl
final case class Bar(a: String, b: String) extends MyADT with BarImpl

// pckg/foo.scala
package pckg

private[pckg] trait FooImpl { self: Foo =>
  ...
}

// pckg/bar.scala
package pckg

private[pckg] trait BarImpl { self: Bar =>
  ...
}

Interesting, and if each of the case classes need to override methods defined in MyADT, can I override them in FooImpl and BarImpl even though those traits don’t inherit from MyADT? And what happens which such a method calls super.myMethod(...) ?

And if each of the case classes need to override methods defined in MyADT

No idea, I have never need to override something in the real sense of OOP. For me or it is abstract or final; and that basic behavior works out of the box.

And what happens which such a method calls super.myMethod(...)?

Again, no idea, never need to call a super method.

Why don’t you give it a try with a small POC and come back here to tell use how it goes.
I know that for some people that would be valuable knowledge.


However, truth is that IMHO what you want reflects a bad design.
ADTs are intended to be simple holder of data with some auxiliary methods, nothing more.
As such, most of us haven’t need to split them into multiple files since they are usually short.
For many of us having complex logic inside an ADT is simply a bad design; I personally would move all your operations into a different object.

I think we’ve talked about this before, but

It’s my experience from writing OO code for 20+ years that methods at the subclass level often need to extend the behavior of less specific methods. To avoid copying code from parent classes into child classes, it is convenient to delegate to the next class in line within the body of a method. This is especially true in systems which allow multiple inheritance as a class may not know who its parent classes are, as they may be mixed-in in different ways by subclasses.

In my mind it is a fundamental feature of an object system.

Yeah and I do not deny it, nor I am saying that OOP is bad.

It is just that I, Luis, have never need that since I started using Scala.

That is why I said that the best would be to make a small POC with all those things you need and post the results here for future reference. (it may even be a great blog post), since I know some people would find that useful (is just that I won’t be one on those; nevertheless I am actually curious about what will be the results).


Note that is why I try to be very explicit about when something is my opinion, and when something is relatively objective.

Just as an aside: There is a school in OOP where (implementation) inheritance is viewed with caution (“favor composition over inheritance”) and overriding method implementations is considered a design smell. This is certainly not universally accepted and maybe not even mainstream, but saying that implementation overrides are fishy by default does not at all amount to saying OOP is bad in general.

1 Like

Yeah I thought on writing which parts of OOP I like / use and which one not, but I thought that was just too off-topic

But yes composition over inheritance is a good point to bring in this discussions. For example, one may say that is the main reason for using case classes / ADTs as just data holders with the minimum functionality.

Overriding implementations is, AFAICT, an essential feature of OO, but calling super-methods less so.

Famous examples of overriding implementations are:

  • toString.
  • To get a concrete sub-class of java.io.InputStream, you only need to override read, which reads a single byte. There is s default implementation for read(Array[Byte]), which calls read() repeatedly, which works but is inefficient for most InputStreams, so you want to override it most of the time.

Calling super methods may be considered a code smell. Typically, foo() performs some action X, and and overriding foo() performs actions X and Y, one might be tempted to call super.foo() in overriding foo(). But then, why not move action X into its own method, to be called from both foo() implementations? What if some one changes the implementation of super.foo().

The way I see this is that if you are the author of the superclass and subclass, then you can change the name of any method. However, if you are a customer extending a class from a vendor, you don’t have permission to change the name of a method in the superclass. Doing so would break all other classes which inherit from it, most notably it will break your customer’s code.

If a class is intended to be abstract, a customer should be able to extend its behavior without access to the source code. (Granted that some classes are not intended to be extended.)

Personally I find the notation super.foo() to be unfortunate and misleading. It is not really the method on the superclass which you want to call; rather it is the next method in a chain of like-named methods determined by the class hierarchy of the object in question. The next method might be the method an other subclass of the superclass, in the case of multiple inheritance. In some cases it might be the method in a class which seems completely unrelated to the class which the code is directly sitting in.