This post describes the reason I asked the question Traits have no parameters.
I had an ADT which several (6) leaf level case classes and two leaf level objects.
sealed abstract class SimpleTypeD { ... }
final case class SAnd(tds: SimpleTypeD*) extends SimpleTypeD { ...}
final case class SOr(tds: SimpleTypeD*) extends SimpleTypeD { ... }
final case class SEql(a:Any) extends SimpleTypeD { ... }
...etc...
The problem was that as the for all the classes was becoming unmanageable as the code was growing larger as I continued to develop the application. So I refactored it to different files. A well-known and apparently much-loved Scala limitation made it impossible to use a sealed class
. (you can’t inherit from a sealed class from a different file.) Thus my ADT was no longer sealed. (so perhaps it should not even be called ADT, not sure). At that point the code was organized into one file containing an abstract class SimpleTypeD
and additionally 8 files, each containing one of the leaf level classes or objects (e.g. SAnd
, SOr
, SEql
, etc).
This solution was usable, but not ideal. I had to do run-time checking for pattern matching exhaustiveness, by added a default error case to the pattern matching clauses. So I finally am trying to do a second refactoring to make an sealed
ATD.
I tried to define SimpleTypeD
as a sealed abstract class
, and define implementation classes as traits (SAndI
, SOrI
, SEqlI
) each name ending in I
for interface. Then I wished to define the leaf classes as follows.
sealed abstract class SimpleTypeD { ... }
final case class SAnd(tds: SimpleTypeD*) extends SimpleTypeD with SAndI(tds : _*)
final case class SOr(tds: SimpleTypeD*) extends SimpleTypeD with SOrI(tds : _*)
final case class SEql(a:Any) extends SimpleTypeD with SEqlI(a)
...etc...
However, this doesn’t work because traits cannot take parameters. So what I did, which seems to accidentally work, but seems convoluted and an abuse is the following.
abstract class SimpleTypeDA { ... } // `A` stands for Abstract
sealed trait SimpleTypeD extends SimpleTypeDA
final case class SOr(tds: SimpleTypeDA*)
extends SOrI(tds : _*) with SimpleTypeD
final case class SAnd(tds: SimpleTypeDA*)
extends SAndI(tds : _*) with SimpleTypeD
final case class SEql(a: Any)
extends SEqlI(a) with SimpleTypeD
Now in different files, define SAndI
, SOrI
, SEqlI
, etc each as abstract class
which extends SimpleTypeDA
. (Yes, SAndI
should be renamed to SAndA
as it is now abstract
rather than an interface.)
abstract class SAndI(tds: SimpleTypeDA*) extends SimpleTypeDA {
...
}
Thus the sealed trait SimpleTypeD
is primarily used to identify which leaf classes are in the ADT. And the fact that SimpleTypeD
extends SimpleTypeDA
forces each of the ADT leaf classes to implement a common set of methods.
One big advantage of this approach, even it is is wierd and abusive and obfuscated, is that if SimpleTypeDA
defines a method foo
then each of the other abstract classes can override the method override def foo
, and each such overridden method can call super.foo(...)
to reach the next method. And the methods seem to all be called in a usable order even if bizzare, i.e, the leaf level method first, and the SimpleTypeDA
next. In fact the order is: SEql
-> SimpleTypeD
-> SEqlI
-> SimpleTypeDA
It is not clear to me why SimpleTypeD
should be considered more specific than SEqlI
, but this minor weirdness doesn’t hinder the semantics of my program.