简体   繁体   English

定义 Scala 特征的隐式视图边界

[英]Defining implicit view-bounds on Scala traits

I'm doing an exercise to implement a functional binary-search-tree in Scala, following a similar pattern that I've seen used in Haskell.我正在做一个练习来在 Scala 中实现一个功能性二叉搜索树,遵循我在 Haskell 中看到的类似模式。 I have a structure that looks something like this:我有一个看起来像这样的结构:

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]()
    ... 
}

I'd like to put a type constraint on A so that it will only accept objects that extend Ordered .喜欢把一个类型约束上A ,这样只会接受扩展对象Ordered It looks like I need to define a view bound on A ( [A <% Ordered[A]] ) on Branch and Leaf , as well as the TreeNode trait.. I can't do this on the TreeNode trait, however, because view bounds aren't accepted.看起来我需要在BranchLeaf上的 A ( [A <% Ordered[A]] )以及TreeNode trait 上定义一个视图边界......但是我不能在TreeNode trait 上这样做,因为不接受视图边界。

As I understand, <% -style view-bounds are syntactic sugar for an implicit definition, so there should be a way to write to define the bound manually within the TreeNode trait.据我了解, <% -style view-bounds 是implicit定义的语法糖,因此应该有一种方法可以在TreeNode trait 中手动定义边界。 I'm not sure how I'm supposed to do this, though.不过,我不确定我该怎么做。 I've looked around a bit, but haven't gotten much further than that some sort of implicit needs to be defined.我环顾四周,但并没有比需要定义某种隐式需要更进一步。

Can anybody point me in the right direction?有人能指出我正确的方向吗? Am I approaching this from the wrong angle entirely?我是否完全从错误的角度接近这个?

The problem is that view bounds as well as context bounds are just syntactic sugar for specific types of implicit parameters.问题是视图边界和上下文边界只是特定类型隐式参数的语法糖。 When applied to a type parameter of a generic class (as opposed to when applied to a generic method), these implicits are added to the constructor of the class.当应用于泛型类的类型参数时(与应用于泛型方法时相反),这些隐式被添加到类的构造函数中。 Because traits have no constructor (or rather, only have a single parameterless constructor), there is nowhere to pass these implicit parameters and thus context bounds and view bounds are illegal on generic traits.因为 trait 没有构造函数(或者更确切地说,只有一个无参数的构造函数),无处传递这些隐式参数,因此上下文边界和视图边界在泛型特征上是非法的。 The simplest solution would be to turn TreeNode into an abstract class.:最简单的解决方案是将TreeNode变成一个抽象类。:

abstract class TreeNode[A <% Ordered[A]]

Note that as advised by Ben James, using a context bound with an Ordering is usually better than a view bound with an Ordered (it is more general).请注意,正如 Ben James 所建议的那样,使用与Ordering绑定的上下文通常比使用Ordered绑定的视图更好(它更通用)。 However the problem is still the same: won't work on a trait.然而,问题仍然是一样的:不适用于特征。

If turning TreeNode into a class is not practical (say you need to mix it at various places in the type hierarchy), you can define an abstract method in TreeNode that will provide the implicit value (of type Ordered[A] ) and have all the classes that extend it define it.如果将TreeNode变成一个类是不切实际的(假设您需要在类型层次结构中的不同位置混合它),您可以在TreeNode中定义一个抽象方法,该方法将提供隐式值(类型为Ordered[A] )并拥有所有扩展它的类定义它。 This unfortunately more verbose and explicit, but you can't do much better in this case:不幸的是,这更加冗长和明确,但在这种情况下你不能做得更好:

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

Note that for a more concise definition, you could equivalently define Leaf like this:请注意,为了更简洁的定义,您可以像这样定义Leaf

case class Leaf[A](implicit protected val toOrdered: A => Ordered[A]) extends TreeNode[A]

You could provide the "evidence" that A is Ordered by requiring an abstract member of type Ordered[A] on the trait :您可以通过在trait上要求一个Ordered[A]类型的抽象成员来提供AOrdered的“证据”:

trait TreeNode[A] {
  implicit val evidence: Ordered[A]
}

You would then be forced to provide this in any concrete subtypes, this proving that A is Ordered :然后你将被迫在任何具体的子类型中提供它,这证明AOrdered

case class Leaf[A](value: A)(implicit ev: Ordered[A]) extends TreeNode[A] {
  val evidence = ev
}

You might instead want to constrain A to a type which has an implicit Ordering[A] - this is not an inheritance relationship;相反,您可能希望将A约束为具有隐式Ordering[A] ——这不是继承关系; it is more like a haskell typeclass.它更像是一个 Haskell 类型类。 But the implementation in terms of the above technique would be the same.但是就上述技术而言,实现将是相同的。

@ben-james 's answer is great, I would like improve it a bit to avoid redundant val s in classes. @ben-james的回答很棒,我想稍微改进一下以避免课堂中出现多余的val

The idea is to define implicit constructor parameter name the same as it is defined in trait that holds implicit value.这个想法是定义隐式构造函数参数名称,与它在包含隐式值的 trait 中定义的相同。

The idea is to avoid this line:这个想法是为了避免这一行:

val evidence = ev

Here is a complete example ( 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}]"
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM