2016-07-22 2 views
-1

Так вот код:Почему общий тип не работает с 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 (в номере класса)] required: week4.expr.Expr [T (в класс Expr)] Примечание: ничего <: T (и week4.expr.Number [T] <: week4.expr.Expr [Nothing]), но класс Expr является инвариантным по типу T. Вы можете задать вместо T + T вместо .

Что я делаю неправильно?

+0

Если предоставленный ответ действительно, было бы неплохо, если бы вы отметили его как принятый. :-) – stefanobaghino

ответ

8

Есть в основном две ошибки в коде:

  • При удлинении 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 по этой теме.

+0

Будет также предлагать читать по типу дисперсии в дополнение к классам типа. – sebszyller

+0

@ 3jckd абсолютно, это может стать довольно важной темой при работе с иерархиями типов. Если вам посчастливилось прочитать, какой тип дисперсии и не понимает, этот образ является моим личным фаворитом, который может прийти на помощь: https://twitter.com/stefanobaghino/status/597016186820423680 – stefanobaghino

+1

Ключевое слово 'new' может быть опущены при создании экземпляров классов case, таких как 'Sum' и' Number', или я чего-то не хватает? –

Смежные вопросы