如何在子类型中限制通用特质

huangapple go评论46阅读模式
英文:

How to restrict generic trait in subtype

问题

我有一个称为Mutable[T]的特质,用于描述可以使用Mutation对象进行变异以获得T类型的对象:

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()
}

// 错误: 无法实例化类Monkey,因为它具有冲突的基类型Mutable[Animal]和Mutable[Mammal]
case class Monkey() extends Mammal {
  override def mutate(mutation: Mutation): Mammal = Human()
}

// 错误: 无法实例化类Human,因为它具有冲突的基类型Mutable[Animal]和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)

希望这些翻译对您有所帮助。

英文:

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)

答案1

得分: 1

错误是由Mutable[Animal]Mutable[Mammal]Monkey的超类型列表中发生冲突引起的。

要解决冲突,您可以改用Mutable[? <: Animal],这与Mutable[Mammal]兼容:

trait Animal extends Mutable[? <: Animal]
trait Mammal extends Animal with Mutable[Mammal]

然后,要扩展类型Animal,您必须显式添加Mutable[Animal]作为超类型,以能够重写mutate方法:

case class Fish() extends Animal with Mutable[Animal] {
  override def mutate(mutation: Mutation): Animal = Fish()
}

可能有助于定义一个辅助特质以减少一些打字量:

trait AnimalBase extends Animal with 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)  // 错误: 找到: (fish : Animal),需要: Nothing
val fish3: Animal = mutation.perform[Animal](fish)  // 错误: 类型参数Animal不符合上限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
英文:

The error is caused by a conflict between Mutable[Animal] and Mutable[Mammal] in the list of supertypes for Monkey.

To resolve the conflict, you can instead use Mutable[? &lt;: Animal] which is compatible with Mutable[Mammal]:

trait Animal extends Mutable[? &lt;: Animal]
trait Mammal extends Animal, Mutable[Mammal]

Then, to extend the type Animal, you have to explicitly add Mutable[Animal] as a supertype to be able to override the mutate method:

case class Fish() extends Animal, Mutable[Animal] {
  override def mutate(mutation: Mutation): Animal = Fish()
}

It might be helpful to define a helper trait to save a bit of typing:

trait AnimalBase extends Animal, Mutable[Animal]
case class Fish() extends AnimalBase {
  override def mutate(mutation: Mutation): Animal = Fish()
}

However, the type of Animal now no longer matches the requirement for Mutation.perform, even if you explicitly specify the type parameter:

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]

This can be fixed by loosening the restriction on Mutation.mutate to [T &lt;: Mutable[? &lt;: T]]:

class Mutation {
  def perform[T &lt;: Mutable[? &lt;: 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

答案2

得分: 1

不要你想使Mutable具有协变性吗?

trait Mutable[+T] {
  def mutate(mutation: Mutation): T
}

在这种情况下,你的代码似乎可以在Scala 3中编译。

当你放宽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)

另外,你还可以尝试使用类型类。

// 类型类
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 = Fishval monkey: Mammal = Monkey)。

英文:

Don't you want to make Mutable covariant?

trait Mutable[+T] {
def mutate(mutation: Mutation): T
}

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.mutate to [T &lt;: Mutable[? &lt;: T]] Mutable[? &lt;: T] is actually defining covariance at a call site

https://stackoverflow.com/questions/75035824/in-scala3-if-generic-type-arguments-is-mapped-to-dependent-type-how-are-cova


Also you can try to make T a type member rather than type parameter. In such case the existential type is just Mutable while a specific type is Mutable { type T = ... } (aka 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 &lt;: Mutable](mutable: Mutable): mutable.T = mutable.mutate(this)
trait Animal extends Mutable:
type T &lt;: Animal
trait Mammal extends Animal:
type T &lt;: 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)

https://stackoverflow.com/questions/75289392/bind-wildcard-type-argument-in-scala (answer)


Also you can try a type class

// 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)

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

https://stackoverflow.com/questions/59813323/advantages-of-f-bounded-polymorphism-over-typeclass-for-return-current-type-prob

huangapple
  • 本文由 发表于 2023年2月6日 04:16:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/75355232.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定