簡體   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