我一直试图解决这个问题,但我似乎找不到解决这个问题的办法。我似乎无法在Scala中正确建模。
假设我有一个特性MyTrait
,其中包含一些实现它的不可变类。
它看起来像这样:
trait MyTrait {
type Repr <: MyTrait
def substitute(original: Item, replacement: Item) : Repr
def substituteAll(
originals: List[Item],
replacement: Item
) : Repr = {
originals match {
case head :: tail => substitute(head).substituteAll(tail, replacement)
case Nil => this //this complains that this is not of type Repr
}
}
}
trait MyTrait2 { ... }
case class MyClassA(originals: List[Item])
extends MyTrait with MyTrait2 {
type Repr = MyClassA
def substitute(original: Item, replacement: Item) : MyClassA = {
//whatever code that updates the list etc.
MyClassA(newOriginals)
}
}
case class MyClassB(originals: List[Item])
extends MyTrait with MyTrait2 {
type Repr = MyClassB
def substitute(original: Item, replacement: Item) : MyClassB = {
//whatever code that updates the list etc.
MyClassB(newOriginals)
}
}
case class CompoundClass(list : List[MyTrait with MyTrait2])
extends MyTrait {
type Repr = CompoundClass
def substitute(
original: Item,
replacement: Item
) : CompoundClass =
CompoundClass(list.map(
myClass => myClass.substitute(original, replacement)
))
)
//the above complains that it is expecting
// List[MyTrait with MyTrait2]
//while in fact it is getting MyTrait2#Repr
}
如果我总结一下我的问题,他们将如下:
通过super-trait方法更新不可变类应该返回相同类型的实现类。
超级特质需要能够在有意义的时候返回this
,而不是返回另一个对象。我似乎对此有疑问。
我需要能够在不知道实际类型的情况下将超级特征传递给函数。标准多态性。那么函数就可以在没有的情况下调用trait的方法 知道实际的具体类型。
我需要能够使用合成将类组合到其他组中。 (想象一下由子表达式组成的表达式)。它似乎是标准的组合,但是上面我用#Repr
我最初尝试在MyTrait[T]
中使用泛型类型,但这使得无法传递我想要的任何具体类。我现在正在尝试使用抽象类型,我似乎在编译时遇到了同样的问题。基本上我认为现在没有任何区别,我再次陷入同样的陷阱。
我做错了什么?我是以错误的方式看待这个吗?
答案 0 :(得分:1)
这里有一些问题,其中一些与多态完全无关!
从substituteAll
方法开始:
trait MyTrait {
type Repr <: MyTrait
def substitute(original: Item, replacement: Item) : Repr
def substituteAll(
originals: List[Item],
replacements: List[Item]
) : Repr = {
originals match {
case head :: tail => substitute(head).substituteAll(tail)
case Nil => this
}
}
}
substitute
和substituteAll
都有两个参数,但你试图用一个参数调用它们。这永远不会奏效!
您还有一个问题,即编译器没有证据this
是Repr
。第一个问题可以很容易地解决,将两个输入压缩成单个元组列表然后使用内部函数,第二个问题可以通过手动提供证据来修复:
trait MyTrait {
type Repr <: MyTrait
def substitute(original: Item, replacement: Item) : Repr
def substituteAll(
originals: List[Item],
replacements: List[Item]
)(implicit typed: this.type => Repr) : Repr = {
def loop(pairs: List[(Item, Item)]): Repr = pairs match {
case (orig, rep) :: tail =>
substitute(orig, rep)
loop(tail)
case Nil => typed(this) //this complains that this is not of type Repr
}
loop(originals zip replacements)
}
}
您的下一个问题是,Repr
类型参数不能保存您希望它为复合类型MyTrait with MyTrait2
保留的内容。
这不是一个完整的类型,因为Repr
param仍然是抽象的。您真正想要的是完全指定的类型MyTrait with MyTrait2 { type Repr = MyTrait with MyTrait2 }
鉴于这有点麻烦,引入另一个特征来表示它更容易:
trait CompoundElem extends MyTrait with MyTrait2 {
type Repr <: CompoundElem
}
然后您可以在其余代码中使用它:
case class MyClassA(originals: List[Item]) extends CompoundElem {
type Repr = MyClassA
def substitute(original: Item, replacement: Item) : MyClassA = {
val newOriginals = originals
MyClassA(newOriginals)
}
}
case class MyClassB(originals: List[Item]) extends CompoundElem {
type Repr = MyClassB
def substitute(original: Item, replacement: Item) : MyClassB = {
val newOriginals = originals
MyClassB(newOriginals)
}
}
case class CompoundClass(list : List[CompoundElem]) extends MyTrait {
type Repr = CompoundClass
def substitute(
original: Item,
replacement: Item
) = CompoundClass(
list.map( _.substitute(original, replacement) )
)
}
如果你想让元素类型更长一些,那么最后一个类也可以写成:
object MyTrait {
//type alias helper to view the type member as though it were a param
//A neat trick, shamelessly borrowed from the shapeless library
type Aux[R] = MyTrait { type Repr = R }
}
case class CompoundClass[E <: MyTrait.Aux[E]](list : List[E]) extends MyTrait {
type Repr = CompoundClass[E]
def substitute(
original: Item,
replacement: Item
) = CompoundClass(
list.map( _.substitute(original, replacement) )
)
}
以这种方式编写,对于大多数情况,您也不需要中间特征。例如,包含CompoundClass
列表的ClassA
将正常工作。
如果你想要List
个ClassA
和ClassB
,那么你仍然需要中间体来帮助编译器准确解释Repr
应该是什么是的,它无法计算Repr
的{{1}}也应该是ClassA with ClassB
。
如果你不介意完全删除元素的类型,你可以这样做:
ClassA with ClassB
它只是case class CompoundClass(list : List[MyTrait]) extends MyTrait {
type Repr = CompoundClass
def substitute(
original: Item,
replacement: Item
) = CompoundClass(
list.map( _.substitute(original, replacement) )
)
}
的{{1}},当您拉动元素以将其视为CompoundClass
或MyTraits
时,您需要使用模式匹配或ClassA
,但您不需要任何中间特征。
最后......作为参考,这里使用F-Bounds重新实现了相同的想法。请注意类型参数如何允许ClassB
,因此您无需在所有子类中明确定义MyTrait2
:
self type