У меня есть черта Mutable[T]
, которая описывает объекты, которые можно преобразовать в T
с помощью объекта Mutation
:
trait Mutable[T] {
def mutate(mutation: Mutation): T
}
class Mutation {
def perform[T <: Mutable[T]](mutable: T): T = mutable.mutate(this)
}
У меня также есть две черты, описывающие животных в целом, а также конкретно млекопитающих.
Я хотел бы потребовать, чтобы Animal
мог мутировать в другой Animal
, но Mammal
может мутировать только в другой Mammal
. Однако следующее не компилируется:
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()
}
Я хотел бы использовать эти типы следующим образом:
val mutation = new Mutation()
val fish: Animal = Fish()
val fish2: Animal = mutation.perform(fish)
val monkey: Mammal = Monkey()
val monkey2: Mammal = mutation.perform(monkey)
@LuisMiguelMejíaSuárez Это связано, но я не хочу возвращать «текущий тип». Чтение это то, что привело к этому вопросу.
Смотрите также обновления
@Nulano конечно, поэтому я добавил комментарий, а не ответ. Кроме того, я хотел также показать, что F-Bound особенно хрупок в этом отношении, и, возможно, класс типов был бы лучшим подходом, или в этом случае this.type
Ошибка вызвана конфликтом между Mutable[Animal]
и Mutable[Mammal]
в списке супертипов для Monkey
.
Чтобы разрешить конфликт, вы можете вместо этого использовать Mutable[? <: Animal]
, который совместим с Mutable[Mammal]
:
trait Animal extends Mutable[? <: Animal]
trait Mammal extends Animal, Mutable[Mammal]
Затем, чтобы расширить тип Animal
, вы должны явно добавить Mutable[Animal]
в качестве супертипа, чтобы иметь возможность переопределить метод mutate
:
case class Fish() extends Animal, Mutable[Animal] {
override def mutate(mutation: Mutation): Animal = Fish()
}
Может быть полезно определить вспомогательный трейт, чтобы сэкономить немного времени на вводе:
trait AnimalBase extends Animal, Mutable[Animal]
case class Fish() extends AnimalBase {
override def mutate(mutation: Mutation): Animal = Fish()
}
Однако тип Animal
теперь больше не соответствует требованию для Mutation.perform
, даже если вы явно указываете параметр типа:
val mutation = new Mutation()
val monkey: Mammal = Monkey()
val monkey2: Mammal = mutation.perform(monkey) // OK
val monkey3: Mammal = mutation.perform[Mammal](monkey) // OK
val fish: Animal = Fish()
val fish2: Animal = mutation.perform(fish) // error: Found: (fish : Animal), Required: Nothing
val fish3: Animal = mutation.perform[Animal](fish) // error: Type argument Animal does not conform to upper bound Mutable[Animal]
Это можно исправить, ослабив ограничение на Mutation.mutate
до [T <: Mutable[? <: T]]
:
class Mutation {
def perform[T <: Mutable[? <: T]](mutable: T): T = mutable.mutate(this)
}
val mutation = new Mutation()
val fish: Animal = Fish()
val fish2: Animal = mutation.perform(fish) // OK
val fish3: Animal = mutation.perform[Animal](fish) // OK
Разве вы не хотите сделать Mutable
ковариантным?
trait Mutable[+T] {
def mutate(mutation: Mutation): T
}
В таком случае ваш код компилируется в Scala 3.
https://scastie.scala-lang.org/qVMDsu7HRLiBFlSchGxWEA
Когда вы ослабляли ограничение от Mutation.mutate
до [T <: Mutable[? <: T]]
Mutable[? <: T]
фактически определяет ковариацию на сайте вызова
Также вы можете попробовать сделать T
членом типа, а не параметром типа. В таком случае экзистенциальный тип — это просто Mutable
, а конкретный тип — Mutable { type T = ... }
(также известный как Mutable.Aux[...]
).
trait Mutable:
type T
def mutate(mutation: Mutation): T
object Mutable:
type Aux[_T] = Mutable { type T = _T }
class Mutation:
def perform[M <: Mutable](mutable: Mutable): mutable.T = mutable.mutate(this)
trait Animal extends Mutable:
type T <: Animal
trait Mammal extends Animal:
type T <: Mammal
case class Fish() extends Animal:
type T = Animal
override def mutate(mutation: Mutation): Animal = Fish()
case class Monkey() extends Mammal:
type T = Mammal
override def mutate(mutation: Mutation): Mammal = Human()
case class Human() extends Mammal:
type T = Mammal
override def mutate(mutation: Mutation): Mammal = Monkey()
val mutation = new Mutation()
val monkey: Mammal = Monkey()
val monkey2: Mammal = mutation.perform(monkey)
val monkey3: Mammal = mutation.perform[Mammal](monkey)
val fish: Animal = Fish()
val fish2: Animal = mutation.perform(fish)
val fish3: Animal = mutation.perform[Animal](fish)
Привязать аргумент подстановочного знака в Scala ( ответ)
Также вы можете попробовать класс типов
// type class
trait Mutable[T]:
type Out
def mutate(t: T, mutation: Mutation): Out
class Mutation:
def perform[T](t: T)(using mutable: Mutable[T]): mutable.Out = mutable.mutate(t, this)
trait Animal
trait Mammal extends Animal
case class Fish() extends Animal
object Fish:
given Mutable[Fish] with
type Out = Fish
def mutate(t: Fish, mutation: Mutation): Out = Fish()
case class Monkey() extends Mammal
object Monkey:
given Mutable[Monkey] with
type Out = Human
def mutate(t: Monkey, mutation: Mutation): Out = Human()
case class Human() extends Mammal
object Human:
given Mutable[Human] with
type Out = Monkey
def mutate(t: Human, mutation: Mutation): Out = Monkey()
val mutation = new Mutation()
val monkey: Monkey = Monkey()
val monkey2: Human = mutation.perform(monkey)
val monkey3: Human = mutation.perform[Monkey](monkey)
val fish: Fish = Fish()
val fish2: Fish = mutation.perform(fish)
val fish3: Fish = mutation.perform[Fish](fish)
Хотя экземпляры классов типов должны разрешаться статически, в то время как вы, похоже, предпочитаете разрешать значения динамически (val fish: Animal = Fish
, val monkey: Mammal = Monkey
).
http://tpolecat.github.io/2015/04/29/f-bounds.html
Преимущества F-ограниченного полиморфизма перед классом типов для задачи возврата текущего типа