I have a trait Mutable[T] that describes objects that can be mutated to T using a Mutation object:
trait Mutable[T] {
def mutate(mutation: Mutation): T
}
class Mutation {
def perform[T <: Mutable[T]](mutable: T): T = mutable.mutate(this)
}
I also have two traits describing animals in general, as well as specifically mammals.
I would like to require that an Animal can mutate into another Animal, but a Mammal can only mutate into another Mammal. However, the following does not compile:
trait Animal extends Mutable[Animal]
trait Mammal extends Animal, Mutable[Mammal]
case class Fish() extends Animal {
override def mutate(mutation: Mutation): Animal = Fish()
}
// error: class Monkey cannot be instantiated since it has conflicting base types Mutable[Animal] and Mutable[Mammal]
case class Monkey() extends Mammal {
override def mutate(mutation: Mutation): Mammal = Human()
}
// error: class Human cannot be instantiated since it has conflicting base types Mutable[Animal] and Mutable[Mammal]
case class Human() extends Mammal {
override def mutate(mutation: Mutation): Mammal = Monkey()
}
I would like to use these types as follows:
val mutation = new Mutation()
val fish: Animal = Fish()
val fish2: Animal = mutation.perform(fish)
val monkey: Mammal = Monkey()
val monkey2: Mammal = mutation.perform(monkey)
Don't you want to make
Mutablecovariant?In such case your code seems to compile in Scala 3
https://scastie.scala-lang.org/qVMDsu7HRLiBFlSchGxWEA
When you were loosening the restriction on
Mutation.mutateto[T <: Mutable[? <: T]]Mutable[? <: T]is actually defining covariance at a call siteIn Scala3, if generic type argument(s) is mapped to dependent type, how are covariant & contravariant modifiers mapped?
Also you can try to make
Ta type member rather than type parameter. In such case the existential type is justMutablewhile a specific type isMutable { type T = ... }(akaMutable.Aux[...])Bind wildcard type argument in Scala (answer)
Also you can try a type class
Although type class instances have to be resolved statically while you seem to prefer resolving values dynamically (
val fish: Animal = Fish,val monkey: Mammal = Monkey).https://docs.scala-lang.org/tutorials/FAQ/index.html#how-can-a-method-in-a-superclass-return-a-value-of-the-current-type
http://tpolecat.github.io/2015/04/29/f-bounds.html
Advantages of F-bounded polymorphism over typeclass for return-current-type problem