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