Почему универсальный тип не работает с Inheritance в 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 {
}
}
за исключением того, что я получаю сообщение об ошибке для всех случаев сравнения:
конструктор не может быть создан для ожидаемого типа; найдено: week4.expr.Number[T(в классе Number)] обязательно: week4.expr.Expr[T(в классе Expr)] Примечание. Ничего<: T (и week4.expr.Number [T] <: week4. expr.Expr [Nothing]), но класс Expr инвариантен в типе T. Вместо этого вы можете определить T как + T.
Что я делаю неправильно?
1 ответ
В вашем коде в основном две ошибки:
- При расширении
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:
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])
) или с помощью так называемой нотации с привязкой к контексту (как в case class Number[T : Numeric]
), который в основном является синтаксическим сахаром для явного варианта, который не допускает явной ссылки на экземпляр класса типа. Я использовал явный вариант в первом случае, потому что мне нужно было сослаться на экземпляр класса типа в моем коде, чтобы фактически сложить два значения (evidence.plus(e1.eval, e2.eval)
) но в последнем случае я использовал нотацию "связанный с контекстом", так как считаю ее более естественной и удобочитаемой.
Если вы хотите, вы также можете реализовать Numeric[T]
для ваших собственных классов (например: Numeric[Rational]
) без необходимости иметь дело со статической иерархией типов.
Это, конечно, очень поспешное объяснение классов типов: для более подробного объяснения я предлагаю вам очень хороший пост в блоге на эту тему.