I'm learning my way through Scala object and type system and as an exercise I decided to implement a Saiyan training application where a Saiyan can train other Saiyans to level up.
class Saiyan
class SuperSaiyan extends Saiyan
class SuperSaiyan2 extends SuperSaiyan
class SuperSaiyan3 extends SuperSaiyan2
trait Trainer[A <: Saiyan]{
def train(trainee: A):A
}
val noob = new Saiyan
val goku = new SuperSaiyan2 with Trainer[SuperSaiyan2] {
//needs override define of train method.
}
val trainedNoob = goku.train(noob)
the Trait Trainer impelimentation is superficial. I want some restrictions on the train method as listed below:
SuperSaiyan2's train method can accept instances of Saiyan,SuperSaiyan and SuperSaiyan2 but not SuperSaiyan3. SuperSaiyan2 cannot train a trainee to become SuperSaiyan3.SuperSaiyan2 takes cannot level down. it must always either level up or stay the same.If possible how can I implement this in this snippet of code?
Specifically you can not logically satisfy this requirements
SuperSaiyan2 instance can train nothing above SuperSaiyan2SuperSaiyan3 instance can train SuperSayian3SuperSaiyan3 is a subtype of SuperSaiyan2As you can see due to 3., a Supersayan3 instance is also a SuperSaiyan2 instance, therefore 1. and 2. makes a contradiction
But you can try to set some bounds, making them abstract on type level, but specified on instance level
The most simple approach that could compile with given terms and example would be
class Saiyan {
type Level <: Saiyan
def train[T <: Saiyan](trainee: T)(implicit ev: Level <:< trainee.Level): Saiyan = Saiyan()
}
object Saiyan {
def apply() = new Saiyan {type Level = Saiyan}
}
class SuperSaiyan extends Saiyan {
type Level <: SuperSaiyan
override def train[T <: Saiyan](trainee: T)(implicit ev: Level <:< trainee.Level): SuperSaiyan = SuperSaiyan()
}
object SuperSaiyan {
def apply() = new SuperSaiyan {type Level = SuperSaiyan}
}
class SuperSaiyan2 extends SuperSaiyan {
type Level <: SuperSaiyan
override def train[T <: Saiyan](trainee: T)(implicit ev: Level <:< trainee.Level): SuperSaiyan2 = SuperSaiyan2()
}
object SuperSaiyan2 {
def apply() = new SuperSaiyan2 {type Level = SuperSaiyan2}
}
class SuperSaiyan3 extends SuperSaiyan2 {
type Level <: SuperSaiyan3
override def train[T <: Saiyan](trainee: T)(implicit ev: Level <:< trainee.Level): SuperSaiyan3 = SuperSaiyan3()
}
object SuperSaiyan3 {
def apply() = new SuperSaiyan3 {type Level = SuperSaiyan3}
}
override def train[T <: Saiyan](trainee: T)(implicit ev: UpperBound <:< trainee.UpperBound): SuperSaiyan3 = SuperSaiyan3()
}
object SuperSaiyan3 {
def apply() = new SuperSaiyan3 {type UpperBound = SuperSaiyan3}
}
this code will succesfully compile code like
val goku = SuperSaiyan2()
goku.train(Saiyan())
but further
goku.train(goku.train(Saiyan()))
will fail, because info about type Level was lost after training. Making this type available after any count of training in my best approach leads to cyclic type references, which is illegal in the current scala.
So if you really want type-level hierarchy with partial order, you can introduce some typeclass for that
class CanTrain[+A, B]
trait SelfCanTrain[T] {
implicit val canTrainSelf: CanTrain[T, T] = new CanTrain
}
class Saiyan {
def train[T](trainee: T)(implicit ev: CanTrain[T, Saiyan]) = new Saiyan
}
object Saiyan extends SelfCanTrain[Saiyan]
class SuperSaiyan extends Saiyan {
def train[T](trainee: T)(implicit ev: CanTrain[T, SuperSaiyan]) = new SuperSaiyan
}
object SuperSaiyan extends SelfCanTrain[SuperSaiyan]
class SuperSaiyan2 extends SuperSaiyan {
def train[T](trainee: T)(implicit ev: CanTrain[T, SuperSaiyan2])= new SuperSaiyan2
}
object SuperSaiyan2 extends SelfCanTrain[SuperSaiyan2]
class SuperSaiyan3 extends SuperSaiyan2 {
def train[T](trainee: T)(implicit ev: CanTrain[T, SuperSaiyan3])= new SuperSaiyan3
}
object SuperSaiyan3 extends SelfCanTrain[SuperSaiyan3]
From now on very transitive
goku.train(goku.train(goku.train(new Saiyan)))
would be succesfully compiled
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