繁体   English   中英

Generics 与 Scala 中的现有类型

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM