您的代码中主要有两个错误:
- 扩展
Expr
时忘记传递类型参数
- 在
Sum
模式匹配的分支中,您试图对两个T
s 求和,但没有向编译器提供足够的证据证明+
运算符是在 type 上定义的T
。
这是一个有效的修订解决方案:
object expr {
abstract class Expr[T](implicit evidence: Numeric[T]) {
def eval: T = this match {
case Number(x) => x
case Sum(e1, e2) => evidence.plus(e1.eval, e2.eval)
}
def show: String = this match {
case Number(x) => "" + x
case Sum(e1, e2) => "(" + e1.show + "+" + e2.show + ")"
}
}
case class Number[T : Numeric](val value: T) extends Expr[T]
case class Sum[T : Numeric](val e1: Expr[T], val e2: Expr[T]) extends Expr[T]
}
这是在 Scala shell 中运行的示例:
scala> import expr._
import expr._
scala> Sum(Sum(Number(2), Number(3)), Number(4))
expression: expr.Sum[Int] = Sum(Sum(Number(2),Number(3)),Number(4))
scala> println(expression.show + " = " + expression.eval)
((2+3)+4) = 9
我确信类型构造函数缺少的类型参数Expr
只是一种干扰,不需要进一步解释。
我示例中的代码介绍了隐式参数evidence
和Numeric
类型类的用法。在 Scala 中,类型类是一种利用特征和隐式参数为具有一定灵活性的类定义功能的模式。
为了对两个通用值求和,编译器必须有办法知道这两个T
s 知道如何求和。但是,数字类型不在类型层次结构中,可以通过说它T
是假设类的子类型Number
(通过编写类似的东西abstract class Expr[T <: Number]
)来利用它。
然而,Scala 标准库引入了Numeric
类型类,它基本上是一个特征,它定义了一组对所有数字类型都有意义的操作(因此得名)。plus
对于想要坚持这个特性的人来说,这个方法是未实现的。
Scala 标准库为各种数字类型实现了这个特征,因此允许您毫不费力地将相同的泛型实现用于各种类型。
这是一个例子:
scala> val expression = Sum(Sum(Number(2.0), Number(3.0)), Number(4.0))
expression: expr.Sum[Double] = Sum(Sum(Number(2.0),Number(3.0)),Number(4.0))
scala> println(expression.show + " = " + expression.eval)
((2.0+3.0)+4.0) = 9.0
可以显式地指定这样的隐式证据(如 中abstract class Expr[T](implicit evidence: Numeric[T])
)或使用所谓的“上下文绑定”表示法(如中case class Number[T : Numeric]
),这基本上是显式变体的语法糖,它放弃显式引用类型类实例。我在第一种情况下使用了显式变体,因为我需要在我的代码中引用类型类实例来实际总结两个值 ( evidence.plus(e1.eval, e2.eval)
),但在后一种情况下我使用了“上下文绑定”表示法,因为我觉得它更自然和可读.
如果您愿意,您也可以Numeric[T]
为自己的类(例如:)实现,Numeric[Rational]
而无需处理静态类型层次结构。
这当然是对类型类的一个非常仓促的解释:为了更彻底的解释,我建议你这篇关于该主题的非常好的博客文章。