英文:
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[? <: Animal]
which is compatible with Mutable[Mammal]
:
trait Animal extends Mutable[? <: 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 <: 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
答案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 = Fish
,val 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 <: Mutable[? <: T]]
Mutable[? <: T]
is actually defining covariance at a call site
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 <: 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)
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
).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论