[英]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
。 看起來我需要在Branch
和Leaf
上的 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]
類型的抽象成員來提供A
是Ordered
的“證據”:
trait TreeNode[A] {
implicit val evidence: Ordered[A]
}
然后你將被迫在任何具體的子類型中提供它,這證明A
是Ordered
:
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.