В вашем коде в основном две ошибки:
- При расширении
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]
) без необходимости иметь дело со статической иерархией типов.
Это, конечно, очень поспешное объяснение классов типов: для более подробного объяснения я предлагаю вам это очень хорошее запись в блоге по теме.
person
stefanobaghino
schedule
22.07.2016