繁体   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