Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala rewriting type parameter of sub type in F-bounded polymorphism

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] {
like image 447
asinkxcoswt Avatar asked Jun 28 '15 08:06

asinkxcoswt


1 Answers

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] {
                                                             ^
like image 195
Régis Jean-Gilles Avatar answered Oct 10 '22 22:10

Régis Jean-Gilles