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

时间:2016-07-22 14:00:16

标签: scala generics 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 {
  }
}

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

  

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

我做错了什么?

1 个答案:

答案 0 :(得分:11)

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

  • 扩展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缺少的类型参数只是分散注意力,并且不需要进一步解释。

我的示例中的代码引入了隐式参数evidence以及Numeric类型类的用法。在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

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

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

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