Почему универсальный тип не работает с 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]) без необходимости иметь дело со статической иерархией типов.

Это, конечно, очень поспешное объяснение классов типов: для более подробного объяснения я предлагаю вам очень хороший пост в блоге на эту тему.

Другие вопросы по тегам