簡體   English   中英

定義 Scala 特征的隱式視圖邊界

[英]Defining implicit view-bounds on Scala traits

我正在做一個練習來在 Scala 中實現一個功能性二叉搜索樹,遵循我在 Haskell 中看到的類似模式。 我有一個看起來像這樣的結構:

trait TreeNode[A] {
    def isLeaf: Boolean
    def traverse: Seq[A]
    ...
}

case class Branch[A](value: A, left: TreeNode[A], right: TreeNode[A]) extends TreeNode[A] { 
   def isLeaf: Boolean = false
   def traverse: Seq[A] = ...
   ... 
}

case class Leaf[A]() extends TreeNode[A] { 
    def isLeaf: Boolean = true
    def traverse: Seq[A] = Seq[A]()
    ... 
}

喜歡把一個類型約束上A ,這樣只會接受擴展對象Ordered 看起來我需要在BranchLeaf上的 A ( [A <% Ordered[A]] )以及TreeNode trait 上定義一個視圖邊界......但是我不能在TreeNode trait 上這樣做,因為不接受視圖邊界。

據我了解, <% -style view-bounds 是implicit定義的語法糖,因此應該有一種方法可以在TreeNode trait 中手動定義邊界。 不過,我不確定我該怎么做。 我環顧四周,但並沒有比需要定義某種隱式需要更進一步。

有人能指出我正確的方向嗎? 我是否完全從錯誤的角度接近這個?

問題是視圖邊界和上下文邊界只是特定類型隱式參數的語法糖。 當應用於泛型類的類型參數時(與應用於泛型方法時相反),這些隱式被添加到類的構造函數中。 因為 trait 沒有構造函數(或者更確切地說,只有一個無參數的構造函數),無處傳遞這些隱式參數,因此上下文邊界和視圖邊界在泛型特征上是非法的。 最簡單的解決方案是將TreeNode變成一個抽象類。:

abstract class TreeNode[A <% Ordered[A]]

請注意,正如 Ben James 所建議的那樣,使用與Ordering綁定的上下文通常比使用Ordered綁定的視圖更好(它更通用)。 然而,問題仍然是一樣的:不適用於特征。

如果將TreeNode變成一個類是不切實際的(假設您需要在類型層次結構中的不同位置混合它),您可以在TreeNode中定義一個抽象方法,該方法將提供隱式值(類型為Ordered[A] )並擁有所有擴展它的類定義它。 不幸的是,這更加冗長和明確,但在這種情況下你不能做得更好:

trait TreeNode[A] {
  implicit protected def toOrdered: A => Ordered[A]
}

case class Branch[A<%Ordered[A]](value: A, left: TreeNode[A], right: TreeNode[A]) extends TreeNode[A] { 
   protected def toOrdered = implicitly[A => Ordered[A]]
}

case class Leaf[A<%Ordered[A]]() extends TreeNode[A] { 
    protected def toOrdered = implicitly[A => Ordered[A]]
}

請注意,為了更簡潔的定義,您可以像這樣定義Leaf

case class Leaf[A](implicit protected val toOrdered: A => Ordered[A]) extends TreeNode[A]

您可以通過在trait上要求一個Ordered[A]類型的抽象成員來提供AOrdered的“證據”:

trait TreeNode[A] {
  implicit val evidence: Ordered[A]
}

然后你將被迫在任何具體的子類型中提供它,這證明AOrdered

case class Leaf[A](value: A)(implicit ev: Ordered[A]) extends TreeNode[A] {
  val evidence = ev
}

相反,您可能希望將A約束為具有隱式Ordering[A] ——這不是繼承關系; 它更像是一個 Haskell 類型類。 但是就上述技術而言,實現將是相同的。

@ben-james的回答很棒,我想稍微改進一下以避免課堂中出現多余的val

這個想法是定義隱式構造函數參數名稱,與它在包含隱式值的 trait 中定義的相同。

這個想法是為了避免這一行:

val evidence = ev

這是一個完整的示例( 要點

trait PrettyPrinted[A] extends (A => String)

object PrettyPrinted {
  def apply[A](f: A => String): PrettyPrinted[A] = f(_)
}

trait Printable[A] {
  implicit def printer: PrettyPrinted[A]
}

// implicit parameter name is important
case class Person(name: String, age: Int)
                 (implicit val printer: PrettyPrinted[Person])
  extends Printable[Person]

object Person {
  implicit val printer: PrettyPrinted[Person] =
    PrettyPrinted { p =>
      s"Person[name = ${p.name}, age = ${p.age}]"
    }
}

// works also with regular classes
class Car(val name: String)
         (implicit val printer: PrettyPrinted[Car])
  extends Printable[Car]

object Car {
  implicit val printer: PrettyPrinted[Car] =
    PrettyPrinted { c =>
      s"Car[name = ${c.name}]"
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM