Есть в основном две ошибки в коде:
- При удлинении
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
является подтипом гипотетического класса 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]
) без необходимости иметь дело с иерархией статического типа.
Это, конечно, очень торопившееся объяснение типов классов: для более подробного объяснения вы предлагаете вам это очень хорошо blog post по этой теме.
Если предоставленный ответ действительно, было бы неплохо, если бы вы отметили его как принятый. :-) – stefanobaghino