我想定义一个抽象类型的抽象递归数据结构。 像这样:
case class ParentA( name : String, children : List[ParentA] ) extends Parent {
type PARENT = ParentA
}
case class ParentB( name : String, children : List[ParentB] ) extends Parent {
type PARENT = ParentB
}
sealed abstract class Parent {
// we'd like to Define Parent as a PARENT
// something like:
// this : PARENT =>
type PARENT <: Parent
val name : String
val children : List[PARENT]
def findParent(name:String) : Option[PARENT] = {
if( name == this.name ) {
Some(this) // ouch
} else {
// re-ouch
children.flatMap( f => f.findParent(name) )
}
}
}
val a2a : ParentA = ParentA("a",List(ParentA("a1",Nil),ParentA("a2",List(ParentA("a2a",Nil))))).findParent("a2a").get
当然这不会编译,因为编译器无法猜测Parent.this是PARENT。
error: type mismatch;
found : Parent.this.type (with underlying type this.Parent)
required: Parent.this.PARENT
Some(this)
还
error: type mismatch;
found : List[Parent.this.PARENT#PARENT]
required: Option[Parent.this.PARENT]
children.flatMap( f => f.findParent(name) )
我可以通过在这里和那里进行转换来解决它,但是能够告诉编译器Parent是PARENT会更好。 或者也许我错过了一些东西:)
我忘了提到泛型不是一种选择。这个例子实际上是一个更微妙的问题的简化版本。使用泛型将导致程序的二次增长。这是一个链接,解释了为什么泛型并不总是一个可行的选择:Scala: Abstract types vs generics。
基本上我最好使用抽象类型 - 甚至抽象类型和泛型 - 以及转换。
答案 0 :(得分:4)
与@Debilski提到的想法相同,但没有额外的类型参数:
case class ParentA(name: String, children: List[ParentA]) extends Parent[ParentA]
case class ParentB(name: String, children: List[ParentB]) extends Parent[ParentB]
sealed abstract class Parent[P <: Parent[P]] { this: P =>
def name: String
def children: List[P]
def findParent(name: String): Option[P] =
if (name == this.name) Some(this)
else children.flatMap(_.findParent(name)).headOption
}
顺便说一句,对抽象成员使用def
而不是val
s。在子类中实现它们时,它们允许更大的灵活性。
答案 1 :(得分:1)
仅供参考。 sschaef有更好的方法。
这使用Scala CRTP和自我类型进行编译。
case class ParentA(name : String, children : List[ParentA]) extends Parent[ParentA]
case class ParentB(name : String, children : List[ParentB]) extends Parent[ParentB]
sealed abstract class Parent[T] { this : T =>
type PARENT = Parent[T] with T
val name : String
val children : List[PARENT]
def findParent(name:String) : Option[PARENT] = {
if( name == this.name ) {
Some(this)
} else {
children.flatMap( f => f.findParent(name) ).headOption
}
}
}
val a2a : ParentA = ParentA("a",List(ParentA("a1",Nil),ParentA("a2",List(ParentA("a2a",Nil))))).findParent("a2a").get