Как я объясняю в мой ответ на другой вопрос, PECS - это мнемоническое устройство, созданное Джошем Блохом, чтобы помочь вспомнить производителя extends
, Consumer super
.
Это означает, что когда параметризованный тип, передаваемый методу, будет выдавать экземпляры из
T
(они будут извлечены из него каким-либо образом), следует использовать? extends T
, поскольку любой экземпляр подклассаT
также являетсяT
.Когда параметризованный тип, передаваемый методу, будет потреблять экземпляры
blockquote>T
(они будут переданы в он должен что-то сделать),? super T
следует использовать, потому что экземплярT
можно законно передать любому методу, который принимает некоторый супертипT
. Например,Comparator
можно использовать наCollection
.? extends T
не будет работать, потому чтоComparator
не может работать наCollection
.Обратите внимание, что обычно вы должны использовать только
? extends T
и? super T
для параметров какого-либо метода. Методы должны использоватьT
только как параметр типа для типичного типа возврата.
Рассмотрим следующее взаимодействие с REPL. Сначала мы определяем класс с факториальным методом:
scala> class C {
def fact(n: Int, result: Int): Int =
if(n == 0) result
else fact(n - 1, n * result)
}
defined class C
scala> (new C).fact(5, 1)
res11: Int = 120
Теперь давайте переопределим его в подклассе, чтобы удвоить ответ суперкласса:
scala> class C2 extends C {
override def fact(n: Int, result: Int): Int = 2 * super.fact(n, result)
}
defined class C2
scala> (new C).fact(5, 1)
res12: Int = 120
scala> (new C2).fact(5, 1)
Какой результат вы ожидаете для этого последний звонок? Вы можете ожидать 240. Но нет:
scala> (new C2).fact(5, 1)
res13: Int = 7680
Это потому, что, когда метод суперкласса делает рекурсивный вызов, рекурсивный вызов проходит через подкласс.
Если переопределение работает так, что 240 был правильным ответом, тогда было бы безопасно, чтобы оптимизация хвостового вызова выполнялась в суперклассе. Но это не так, как работает Scala (или Java).
Если метод не помечен как final, он может не вызывать себя , когда он делает рекурсивный вызов.
И вот почему @tailrec не работает, если метод не является окончательным (или закрытым).
UPDATE: Я рекомендую прочитать другие два ответа (John's and Rex's).
Что именно пойдет не так, если компилятор применил TCO в таком случае?
blockquote>Ничто не пойдет не так. Любой язык с правильным устранением вызова хвоста сделает это (SML, OCaml, F #, Haskell и т. Д.). Единственная причина, по которой Scala не заключается в том, что JVM не поддерживает хвостовую рекурсию, и обычный взлом Scala для замены саморекурсивных вызовов в хвостовой позиции с
goto
не работает в этом случае. Scala в CLR может сделать это, как F #.