Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

forcing argument and return type restrictions on a function based on it's parameterized types

Tags:

scala

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:

  1. the SuperSaiyan2's train method can accept instances of Saiyan,
    SuperSaiyan and SuperSaiyan2 but not SuperSaiyan3.
  2. SuperSaiyan2 cannot train a trainee to become SuperSaiyan3.
  3. Whatever trainee the 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?

like image 496
shayan Avatar asked Jan 28 '26 23:01

shayan


1 Answers

Disclaimer: This problem is unsolvable in the given terms.

Specifically you can not logically satisfy this requirements

  1. any SuperSaiyan2 instance can train nothing above SuperSaiyan2
  2. SuperSaiyan3 instance can train SuperSayian3
  3. SuperSaiyan3 is a subtype of SuperSaiyan2

As 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

UPDATE:

Hierarchy semi-solution

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.

Typeclass

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

like image 113
Odomontois Avatar answered Jan 31 '26 14:01

Odomontois