[英]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.