代码之家  ›  专栏  ›  技术社区  ›  Nulano

如何限制亚型中的共性特征

  •  0
  • Nulano  · 技术社区  · 3 年前

    我有一个特点 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 可以变异成另一个 动物 ,但是 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)
    
    1 回复  |  直到 3 年前
        1
  •  1
  •   Dmytro Mitin    3 年前

    难道你不想 Mutable 协变的?

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

    在这种情况下,您的代码似乎是在Scala 3中编译的

    https://scastie.scala-lang.org/qVMDsu7HRLiBFlSchGxWEA


    当你放松对 Mutation.mutate 到 [T <: Mutable[? <: T]] Mutable[? <: T] 实际上是在呼叫站点定义协方差

    In Scala3, if generic type argument(s) is mapped to dependent type, how are covariant & contravariant modifiers mapped?


    你也可以试着 T 类型成员而不是类型参数。在这种情况下,存在类型只是 可突变的 而特定类型是 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)
    

    Bind wildcard type argument in Scala ( answer )


    你也可以尝试一个类型类

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

    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

        2
  •  1
  •   Nulano    3 年前

    错误是由以下两者之间的冲突引起的 Mutable[Animal] 和 Mutable[Mammal] 在的超类型列表中 Monkey 。

    要解决冲突,您可以使用 Mutable[? <: Animal] 与兼容 可突变[哺乳动物] :

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

    然后,扩展类型 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()
    }
    

    然而 动物 现在不再符合 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