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

Him*_*hra 1 generics scala invariants covariant

所以这是代码:

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 {
  }
}
Run Code Online (Sandbox Code Playgroud)

除了我得到所有案例比较的错误:

构造函数无法实例化为期望的类型; 发现:week4.expr.Number [T(在类Number中)] required:week4.expr.Expr [T(在类Expr中)]注意:Nothing <:T(和week4.expr.Number [T] <:week4. expr.Expr [Nothing]),但类Expr在类型T中是不变的.您可能希望将T定义为+ T.

我究竟做错了什么?

ste*_*ino 12

您的代码中主要有两个错误:

  • 扩展Expr时忘记传递类型参数
  • Sum模式匹配的分支中,您试图将两个Ts 相加,而没有向编译器提供足够的证据证明+在类型上定义了运算符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]

}
Run Code Online (Sandbox Code Playgroud)

以下是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
Run Code Online (Sandbox Code Playgroud)

我确定类型构造函数缺少的类型参数Expr只是一个分心,它不需要进一步解释.

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

为了对两个通用值求和,编译器必须有办法知道两者T知道如何求和.但是,数字类型不在类型层次结构中,可以通过说这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
Run Code Online (Sandbox Code Playgroud)

指定这样的隐式证据可以显式地(如在abstract class Expr[T](implicit evidence: Numeric[T]))中或通过使用所谓的"上下文绑定"表示法(如在case class Number[T : Numeric])中进行,这基本上是显式变体的语法糖,它放弃了显式引用类型类实例.我在第一种情况下使用了显式变体,因为我需要在我的代码中引用类型类实例来实际求和两个值(evidence.plus(e1.eval, e2.eval))但在后一种情况下我使用了"上下文绑定"符号,因为我觉得它更自然和可读.

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

这当然是类型类的一个非常仓促的解释:为了更彻底的解释,我建议你这篇关于这个主题的非常好的博客文章.