I am trying to create a trait Entity
which enforces its sub types to have 2 states: Transient
and Persistent
trait EntityState
trait Transient extends EntityState
trait Persistent extends EntityState
trait Entity[State <: EntityState]
For example, a sub class, says class Post[State <: EntityState] extends Entity[State]
, can be instantiated either as new Post[Persistent]
or as new Post[Transient]
.
Next, I am adding some methods to the trait Entity
that can be called depending on its State
:
trait Entity[State <: EntityState] {
def id(implicit ev: State <:< Persistent): Long
def persist(implicit ev: State <:< Transient): Entity[Persistent]
}
To explain, for any class that extends Entity
, the method id
can be called only when the class is of state Persistent
(i.e. it has been saved to the database and already assigned an autogenerated id).
On the other hand, the method persist
can be called only when the class is Transient
(not yet saved to the database). The method persist
is meant to save an instance of the caller class to the database and return the Persistent
version of the class.
Now, the problem is I would like that the return type of persist
be that of the caller class. For example, if I call persist
on an instance of class Post[Transient]
, it should return Post[Persistent]
instead of Entity[Persistent]
.
I searched around and found something called F-Bounded Polymorphism. I am trying many ways to adapt it to solve my problem but still not works. Here is what I did:
First try:
trait Entity[State <: EntityState, Self[_] <: Entity[State,Self]] {
def id(implicit ev: State <:< Persistent): Long
def persist(implicit ev: State <:< Transient): Self[Persistent]
}
and
class Post[State <: EntityState] extends Entity[State, ({type λ[B] == Post[State]})#λ] {
def persist(implicit ev: <:<[State, Transient]): Post[State] = {
???
}
}
In the class Post
above, I use auto-completion of Eclipse to generate the implementation of the method persist
and found that its return type is still incorrect.
Second try:
class Post[State <: EntityState] extends Entity[State, Post] {
def persist(implicit ev: <:<[State, Transient]): Post[Persistent] = {
???
}
}
with this, it seems correct, except it has a compilation error:
[error] D:\playspace\myblog\app\models\post\Post.scala:14: kinds of the type arguments (State,models.post.Post) do not conform to the expected kinds of the type parameters (type State,type Self) in trait Entity.
[error] models.post.Post's type parameters do not match type Self's expected parameters:
[error] type State's bounds <: common.models.EntityState are stricter than type _'s declared bounds >: Nothing <: Any
[error] trait Post[State <: EntityState] extends Entity[State, Post] {
I believe this is what you were trying to do:
trait Entity[State <: EntityState, Self[S<:EntityState] <: Entity[S, Self]] {
_: Self[State] =>
def id(implicit ev: State <:< Persistent): Long
def persist(implicit ev: State <:< Transient): Self[Persistent]
}
abstract class Post[State <: EntityState] extends Entity[State, Post] {
def persist(implicit ev: <:<[State, Transient]): Post[Persistent] = {
???
}
}
UPDATE: The _: Self[State] =>
part is a self-type annotation. It says that any class that mixes the Entity
trait must extend Self[State]
(not doing so would lead to a compile-time error). If we remove this self-type annotation, we might define something like this and the compiler would not blink an eye:
abstract class User[State <: EntityState] extends Entity[State, Post] {
def persist(implicit ev: <:<[State, Transient]): Post[Persistent] = {
???
}
}
Note how out User
class extends Entity
with the Self
type parameter set to Post
(instead of User
). This is valid as far as the compiler is concerned, but was certainly not what you had in mind.
With the self-type annotation, the above would not compile:
<console>:12: error: illegal inheritance;
self-type User[State] does not conform to Entity[State,Post]'s selftype Post[State]
abstract class User[State <: EntityState] extends Entity[State, Post] {
^
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With