我正在练习在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
的对象。看起来我需要在[A <% Ordered[A]]
和Branch
上的A (Leaf
)以及TreeNode
特征上定义绑定视图。但是,我无法在TreeNode
特征上执行此操作,因为不接受视图边界。
据我所知,<%
- 样式视图边界是implicit
定义的语法糖,因此应该有一种方法可以在TreeNode
特征内手动定义边界。不过,我不确定我该怎么做。我已经环顾了一下,但没有比某种隐含的需要定义更多的东西。
有人能指出我正确的方向吗?我是从错误的角度接近这个吗?
答案 0 :(得分:18)
问题在于视图边界和上下文边界只是特定类型的隐式参数的语法糖。当应用于泛型类的类型参数时(与应用于泛型方法时相反),这些含义将添加到类的构造函数中。
因为traits没有构造函数(或者更确切地说,只有一个无参数构造函数),所以无处可传递这些隐式参数,因此上下文边界和视图边界在通用特征上是非法的。
最简单的解决方案是将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]]
}
答案 1 :(得分:10)
您可以通过在A
上要求Ordered
类型的抽象成员来提供Ordered[A]
为trait
的“证据”:
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类型类。但就上述技术而言,实施方案将是相同的。
答案 2 :(得分:1)
@ben-james的答案很棒,我想稍微改进一下,以避免课程中出现多余的val
。
我们的想法是定义隐式构造函数参数名称,与在包含隐式值的trait中定义的名称相同。
我们的想法是避免这一行:
val evidence = ev
这是一个完整的示例(gist)
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}]"
}
}