[英]Generics with Existential Types in Scala
一個關於兩個特征的故事,看起來應該很好地結合在一起,但沒有,而且我無法弄清楚為什么這段代碼不起作用,或者編譯錯誤真的試圖告訴我什么。
所以......我們有
父母的特質...
trait PolyTreeHasParents[P <: PolyTreeHasChildren[_]] {
val _parents: ListBuffer[P] = ListBuffer()
def isRootNode = _parents.size == 0
def parents: List[P] = _parents.readOnly
def addParent(parent: P): PolyTreeHasParents[P] = {
println(parent)
if (parent == this)
throw new IllegalArgumentException()
_parents += parent
//
this
}
}
和孩子們的特質……
trait PolyTreeHasChildren[C <: PolyTreeHasParents[_]] {
val _children: ListBuffer[C] = ListBuffer()
def isLeafNode = children == ListBuffer()
def children: List[C] = _children.readOnly
def += (child: C) : PolyTreeHasChildren[C] = {
addChild(child)
}
def addChild(child: C): PolyTreeHasChildren[C] = {
if (child == this)
throw new IllegalArgumentException()
_children += child
child.addParent(this) // <= ERROR HERE
this
}
}
指向 ERROR 的指針告訴我們發現了類型不匹配。
PolyTreeHasChildren.this.type(with underlying type PolyTreeHasChildren[C]) required: _$1 where type _$1
我本來以為添加
P :< PolyTreeHasParents[_]
本來可以讓我添加對孩子父母的引用。
這是奇怪的部分......回顧一下,錯誤是:
required: _$1 where type _$1
什么??
唉......我已經沒有關於如何使這段代碼工作的想法了:(
您可以通過兩種方式避免這種明顯的無限循環:
首先,刪除不必要的類型界限( https://stackoverflow.com/questions/1332574/common-programming-mistakes-for-scala-developers-to-avoid/5602321#5602321 ) - 至少在您當前的PolyTreeHasParents
實現中,無需說P
必須是PolyTreeHasChildren
的子類型。
其次,您可以向PolyTreeHasChildren
添加另一個類型參數,指定實現類型,並將其用作自類型。 我認為這是 collections 庫中的常見模式。
它看起來像這樣:
import collection.mutable.ListBuffer
trait PolyTreeHasParents[P] {
val _parents: ListBuffer[P] = ListBuffer()
def isRootNode = _parents.size == 0
def parents: List[P] = _parents.readOnly
def addParent(parent: P): PolyTreeHasParents[P] = {
require (parent != this)
_parents += parent
this
}
}
trait PolyTreeHasChildren[Repr, C <: PolyTreeHasParents[Repr]] {
me: Repr =>
val _children: ListBuffer[C] = ListBuffer()
def isLeafNode = children == ListBuffer()
def children: List[C] = _children.readOnly
def += (child: C) : Repr = {
addChild(child)
}
def addChild(child: C): Repr = {
require (child != this)
_children += child
child.addParent(this)
this
}
}
錯誤信息看起來很奇怪,但這種瘋狂是有方法的。 它說的是:您正在傳遞某種已知類型的參數。 但是您已將其指定為某種未知類型的參數,在本例中將其 skolomized 為 _$1。
在您的代碼中,您可以完全擺脫類型參數:
trait PolyTreeHasParents {
type P = PolyTreeHasChildren
val _parents: ListBuffer[P] = ListBuffer()
def isRootNode = _parents.size == 0
def parents: List[P] = _parents.readOnly
def addParent(parent: P): PolyTreeHasParents = {
if (!_parents.contains(parent)) {
println(parent)
if (parent == this) throw new IllegalArgumentException()
_parents += parent
parent.addChild(this)
}
this
}
}
trait PolyTreeHasChildren {
type C = PolyTreeHasParents
val _children: ListBuffer[C] = ListBuffer()
def isLeafNode = children == ListBuffer()
def children: List[C] = _children.readOnly
def +=(child: C): PolyTreeHasChildren = {
addChild(child)
}
def addChild(child: C): PolyTreeHasChildren = {
if (!_children.contains(child)) {
println(child)
if (child == this)
throw new IllegalArgumentException()
_children += child
child.addParent(this)
}
this
}
}
看到這種行為:
object Test {
def main(args: Array[String]) {
trait X extends PolyTreeHasParents with PolyTreeHasChildren
trait Y extends PolyTreeHasParents with PolyTreeHasChildren
val x0, x1, x2 = new X {}
val y0, y1, y2 = new Y {}
x0.addChild(x1)
x1.addChild(x2)
y2.addParent(y1)
y1.addParent(y0)
x0.addParent(y2)
}
}
現在讓我們將其與“nm”的解決方案的行為進行比較:
object Test {
def main(args: Array[String]) {
trait X extends PolyTreeHasParents[X, X] with PolyTreeHasChildren[X, X]
trait Y extends PolyTreeHasParents[Y, Y] with PolyTreeHasChildren[Y, Y]
val x0, x1, x2 = new X {}
val y0, y1, y2 = new Y {}
x0.addChild(x1)
x1.addChild(x2)
y2.addParent(y1)
y1.addParent(y0)
// x0.addParent(y2) // will not compile
}
}
你有沒有嘗試過類似的東西
trait PolyTreeHasParents[P <: PolyTreeHasChildren[PolyTreeHasParents[P]]] { }
trait PolyTreeHasChildren[C <: PolyTreeHasParents[PolyTreeHasChildren[C]]] { }
存在類型在這里不好。 您本質上說“我的父母有一些不知名的孩子”。 你應該說“我父母有像我一樣的孩子”。
免責聲明:我還沒有機會對此進行測試(附近沒有真正的電腦,只有手機)。
更新:不,這不起作用。 如果您仍然感興趣,下面是一些工作代碼。 我已將它對稱添加(addParent 調用 addChild,addChild 調用 addParent)以說明在真正需要循環依賴時它是如何工作的。 我使用了 0__ 的想法,即在其中注入 sel-type。 導入 scala.collection.mutable.ListBuffer
trait PolyTreeHasParents[Repr <: PolyTreeHasParents[Repr, P],
P <: PolyTreeHasChildren[P, Repr]] {
me: Repr =>
val _parents: ListBuffer[P] = ListBuffer()
def isRootNode = _parents.size == 0
def parents: List[P] = _parents.readOnly
def addParent(parent: P): Repr = {
if (! _parents.contains(parent) {
println(parent)
if (parent == this)
throw new IllegalArgumentException()
_parents += parent
parent.addChild(this)
}
this
}
}
trait PolyTreeHasChildren[Repr <: PolyTreeHasChildren[Repr, C],
C <: PolyTreeHasParents[C, Repr]] {
me: Repr =>
val _children: ListBuffer[C] = ListBuffer()
def isLeafNode = children == ListBuffer()
def children: List[C] = _children.readOnly
def += (child: C) : PolyTreeHasChildren[Repr, C] = {
addChild(child)
}
def addChild(child: C): Repr = {
if (! _children.contains(child) {
println(child)
if (child == this)
throw new IllegalArgumentException()
_children += child
child.addParent(this)
}
this
}
}
// Usage example
class PP extends PolyTreeHasChildren[PP, CC]
class CC extends PolyTreeHasParents[CC, PP]
類型參數邊界的復雜性源於您的 PolyTree 由 2 個特征組成的事實。 一個包含父母,另一個包含孩子。
我不明白擁有這些單獨特征的用例可能是什么。 因為在 PolyTree 中,一般來說,所有節點都可能有子節點和/或父節點。 所以我認為一個人總是會把它們混合在一起。
如果是這種情況,那么可以擺脫大部分類型參數界限的復雜性:
trait PolyTree[Self <: PolyTree[Self] ] {
self: Self =>
private val _parents: ListBuffer[Self] = ListBuffer()
def isRootNode = _parents.isEmpty
def parents: List[Self] = _parents.readOnly
def addParent(parent: Self): Self = {
if (!_parents.contains(parent)) {
println(parent)
if (parent == this) throw new IllegalArgumentException()
_parents += parent
parent.addChild(this)
}
this
}
private val _children: ListBuffer[Self] = ListBuffer()
def isLeafNode = _children.isEmpty
def children: List[Self] = _children.readOnly
def addChild(child: Self): Self = {
if (!_children.contains(child)) {
println(child)
if (child == this)
throw new IllegalArgumentException()
_children += child
child.addParent(this)
}
this
}
}
用例:
object UseCasePolyTree {
trait X extends PolyTree[X]
trait Y extends PolyTree[Y]
val x0, x1, x2 = new X {}
val y0, y1, y2 = new Y {}
x0.addChild(x1)
x1.addChild(x2)
val xx1: X = x2.parents.head
y2.addParent(y1)
y1.addParent(y0)
// x0.addParent(y2) // will not compile
}
除此之外:多樹是無環的。 您仍然應該添加代碼以防止創建循環。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.