在Scala特征上定义隐式视图边界

时间:2013-01-23 15:54:04

标签: scala generics types

我正在练习在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特征内手动定义边界。不过,我不确定我该怎么做。我已经环顾了一下,但没有比某种隐含的需要定义更多的东西。

有人能指出我正确的方向吗?我是从错误的角度接近这个吗?

3 个答案:

答案 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]
}

然后您将被迫以任何具体的子类型提供此功能,这证明AOrdered

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}]"
    }
}