JulienD JulienD - 1 month ago 7
Scala Question

Scala: force type parameter to be a case class

I have an abstract class

Model
from which I create case classes:

abstract class Model
case class User(.) extends Model


an abstract class
Table
taking such a
Model
as type parameter, used in one of its default concrete methods:

abstract class Table[M <: Model] {
def parser = SomeExternalBuilder[M]
}


The meaning is rather simple: "Give every instance of
Table
a default
parser
based on its own class".

The problem is that
SomeExternalBuilder
will only accept a case class as argument ("case class expected: M"), so it does not compile.

Can I make
Table
take only case classes as type parameter?

I have seen a few answers providing a missing
copy
method (ref1, ref2), so I tried this:

trait Model[T] {
def copy: T
}

abstract class Table[M <: Model[M]]


but now case class
User
extends
Model[User]
and must overwrite
copy
too, every function creating a
Model
takes a type parameter, and honestly the code quickly starts being atrocious, all that for that single line in
Table
.

Is there no better way than copying that
def parser
line in every child's body?

Edit: N.B. The real function is
def parser: anorm.Macro.namedParser[M]
from the "anorm" library for Play.

Edit: Source of the type check by this macro: https://github.com/playframework/anorm/blob/0a1b19055ba3e3749044ad8a54a6b2326235f7c8/core/src/main/scala/anorm/Macro.scala#L117

Answer

The problem is that SomeExternalBuilder will only accept a case class as argument ("case class expected: M"), so it does not compile.

I don't think you can ever get such a message from Scala compiler itself, which means that SomeExternalBuilder.apply is a macro. It requires a specific case class in order to know its fields, so that it doesn't matter if you could limit M to be a case class (which you can't): it still wouldn't accept a type parameter.

What you can do is create a macro annotation, so that when you write e.g.

@HasModel
class SomeTable extends Table[SomeModel] {
  ...
}

the val parser = namedParser[SomeModel] is generated automatically. Alternately, write @HasModel[SomeModel] class SomeTable { ... } and generate extends Table[SomeModel] as well.

It wouldn't be hard (as macros go), but you still need to annotate each class extending Table.