为什么泛型类型不能在 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
只是一种干扰,不需要进一步解释。
我例子中的代码引入了隐式参数evidence
和Numeric
类型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。
代码如下:
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
只是一种干扰,不需要进一步解释。
我例子中的代码引入了隐式参数evidence
和Numeric
类型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。