Правая сторона метода (сопоставление с образцом)
t match {
case Empty => Empty
case NonEmpty(elem, left, right) =>
if (elem < 0) throw new Exception
else NonEmpty(elem, assertNonNegatve(left), assertNonNegative(right))
}
означает проверку во время выполнения, является ли t
экземпляром класса Empty$
(объект Empty
), а затем выберите первую ветвь или в противном случае, является ли t
экземпляром класса NonEmpty
, а затем выберите вторую ветвь.
Подпись
def assertNonNegative[S <: IntTree](t: S): S
означает проверку во время компиляции, что для каждого типа S
, который является подтипом типа IntTree
, если метод принимает параметр t
типа S
, тогда метод возвращает значение типа S
.
Код не компилируется из-за определения метод не соответствует его сигнатуре. Подклассами IntTree
являются NonEmpty
и Empty
(объект). Если IntTree
не запечатано, вы можете создавать его подклассы, отличные от Empty
и NonEmpty
, вы даже можете создавать их динамически во время выполнения. Но давайте даже предположим, что IntTree
запечатан, а Empty
и NonEmpty
- его единственные подклассы.
Дело в том, что существует множество подтипов IntTree
(классы и типы разные ): IntTree
, Empty.type
, NonEmpty
, Nothing
, Null
, Empty.type with NonEmpty
, NonEmpty with SomeType
, Empty.type with SomeType
, IntTree with SomeType
, T
(type T <: IntTree
), x.type
(val x: IntTree = ???
) и c. и для всех должно выполняться условие (t: S): S
.
Очевидно, что это не так. Например, можно взять t = Empty.asInstanceOf[Empty.type with Serializable]
. Имеет тип Empty.type with Serializable
. Во время выполнения он соответствует классу Empty
(объект), поэтому выбирается первая ветвь. Но во время компиляции мы этого еще не знаем, как вы можете гарантировать, что во время компиляции оба возвращенных Empty
и NonEmpty
имеют тип Empty.type with Serializable
?
Несоответствие типов в аннотации тип, используемый в сопоставлении с образцом
Один из способов исправить assertNonNegative
- написать честные monomorphi c
def assertNonNegative(t: IntTree): IntTree = {
t match {
case Empty => Empty
case NonEmpty(elem, left, right) =>
if (elem < 0) throw new Exception
else NonEmpty(elem, assertNonNegative(left), assertNonNegative(right))
}
}
, другой - притвориться, что polymorphi c подпись верна
def assertNonNegative[S <: IntTree](t: S): S = {
(t match {
case Empty => Empty
case NonEmpty(elem, left, right) =>
if (elem < 0) throw new Exception
else NonEmpty(elem, assertNonNegative(left), assertNonNegative(right))
}).asInstanceOf[S]
}
третий - использовать теги типов
def assertNonNegative[S <: IntTree : TypeTag](t: S): S = {
t match {
case Empty if typeOf[S] == typeOf[Empty.type] => Empty.asInstanceOf[S]
case NonEmpty(elem, left, right) if typeOf[S] == typeOf[NonEmpty] =>
if (elem < 0) throw new Exception
else NonEmpty(elem, assertNonNegative(left), assertNonNegative(right)).asInstanceOf[S]
case _ => ???
}
}
четвертый - сделать ADT более типовым
sealed trait IntTree
object Empty extends IntTree
case class NonEmpty[L <: IntTree, R <: IntTree](elem: Int, left: L, right: R) extends IntTree
и определить класс типа
def assertNonNegative[S <: IntTree](t: S)(implicit ann: AssertNonNegative[S]): S = ann(t)
trait AssertNonNegative[S <: IntTree] {
def apply(t: S): S
}
object AssertNonNegative {
implicit val empty: AssertNonNegative[Empty.type] = { case Empty => Empty }
implicit def nonEmpty[L <: IntTree : AssertNonNegative,
R <: IntTree : AssertNonNegative]: AssertNonNegative[NonEmpty[L, R]] = {
case NonEmpty(elem, left, right) =>
if (elem < 0) throw new Exception
else NonEmpty(elem, assertNonNegative(left), assertNonNegative(right))
}
}
Надежность системы типов означает, что иногда мы отклоняем некоторые программы во время компиляции, в то время как они не могут go ошибаться во время выполнения. Например
val x: Int = if (true) 1 else "a"