为什么泛型类型不能在 Scala 中使用继承?

Why don't generic types work with inheritance in Scala?

代码如下:

package week4
object expr {
  abstract class Expr[T] {
    def eval:T = this match {
      case Number(x)   => x
      case Sum(e1, e2) => 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](val value: T) extends Expr {
  }
  case class Sum[T](val e1: Expr[T], val e2: Expr[T]) extends Expr {
  }
}

除非我在所有案例比较中得到错误:

constructor cannot be instantiated to expected type; found : week4.expr.Number[T(in class Number)] required: week4.expr.Expr[T(in class Expr)] Note: Nothing <: T (and week4.expr.Number[T] <: week4.expr.Expr[Nothing]), but class Expr is invariant in type T. You may wish to define T as +T instead.

我做错了什么?

你的代码主要有两个错误:

  • 扩展Expr时忘记传递类型参数
  • 在模式匹配的 Sum 分支中,您试图对两个 T 求和,但没有向编译器提供足够的证据证明 + 运算符已定义在类型 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 只是一种干扰,不需要进一步解释。

我例子中的代码引入了隐式参数evidenceNumeric类型class的用法。在 Scala 中,类型 class 是一种模式,它利用特征和隐式参数来定义具有一定灵活性的 classes 的功能。

为了求和两个泛型值,编译器必须有办法知道两个 T 知道如何求和。然而,数字类型不在可以通过说 T 是假设的 class Number 的子类型(通过写类似 abstract class Expr[T <: Number] 的东西)来利用的类型层次结构中。

然而,Scala 标准库引入了 Numeric 类型 class,它基本上是一种特征,它定义了一组对所有数字类型(因此得名)都有意义的操作。 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]) 中)或使用所谓的 "context bound" 符号(如 case class Number[T : Numeric] 中)来指定这样的隐式证据,这基本上是句法的放弃显式引用类型 class 实例的显式变体的糖。我在第一种情况下使用了显式变体,因为我需要在我的代码中引用类型 class 实例来实际求和两个值 (evidence.plus(e1.eval, e2.eval)),但我在后一种情况,因为我觉得它更自然和可读。

如果您愿意,您也可以为自己的 classes(例如:Numeric[Rational])实现 Numeric[T],而无需处理静态类型层次结构。

这当然是对类型 classes 的非常仓促的解释:为了获得更详尽的解释,我建议您在这个主题上非常好 blog post