Проблема компиляции в Scala с F-ограниченными типами и экзистенциальными типами
Я использую F-ограниченный тип для того, чтобы иметь возможность вернуть текущий тип
trait Board[T <: Board[T]] {
def updated : T
}
И я пытаюсь написать универсальный вспомогательный метод, который использует его.
Вопрос в следующем: почему следующее не компилируется?
object BoardOps {
def updated(board: Board[_]) = {
board.updated.updated
}
}
Ошибка value updated is not a member of _$1
Я понял эти 2 обходных пути. Они эквивалентны?
object BoardOps {
def updated(board: Board[_<:Board[_]]) = {
board.updated.updated
}
}
object BoardOps {
def updated[T <: Board[T]](board: T) : T = {
board.updated.updated
}
}
2 ответа
почему следующее не компилируется?
С помощью Board[_]
как тип параметра говорит компилятору "мне все равно, какой тип параметра внутри платы". А именно, это, для компилятора, является экзистенциальным типом, он не знает никаких особенностей об этом типе. В качестве таких, board.updated
возвращает "невыразимый" или непрозрачный тип, потому что мы сказали компилятору "выбросить" эту информацию о типе.
Я понял эти 2 обходных пути. Они эквивалентны?
Ваш предыдущий пример использует экзистенциальный тип с ограничением, чтобы быть подтипом Board[_]
или более формально пишем:
Board[T] forSome { type T <: Board[U] }
Где на самом деле называет компилятор T -> $_1
а также U -> $_2
Опять же, мы ничего не знаем о параметре внутреннего типа, только о том, что он имеет верхнюю границу Board[_]
, В последнем примере используется универсально определенный тип с именем T
, который компилятор может использовать для определения типа возвращаемого значения метода определенного типа T
скорее, чем Any
,
Чтобы ответить на ваш вопрос, нет, они не эквивалентны. Экзистенциальные и универсально выраженные типы двойственны друг другу. Дополнительную информацию об экзистенциалах можно найти в разделе Что такое экзистенциальный тип? и https://www.drmaciver.com/2008/03/existential-types-in-scala/
Не компилируется, потому что как только пишешь Board[_]
, компилятор не делает ничего полезного о параметре анонимного типа _
,
Есть несколько обходных путей (которые не совпадают с предложенными вами):
- использование
Board[X] forSome { type X <: Board[X] }
- Используйте сопоставление с образцом, чтобы получить больше информации о типе
С помощью forSome
экзистенциальная количественная оценка
Это можно легко исправить с помощью forSome
экзистенциальное количественное определение:
import scala.language.existentials
object BoardOps_forSome {
def updated(board: Board[X] forSome { type X <: Board[X] }) = {
board.updated.updated.updated
}
}
Это позволяет вам вызывать updated
неограниченное количество раз.
Использование сопоставления с образцом
Вы можете обойти это без изменения сигнатуры, используя сопоставление с образцом. Например, эта безбожная конструкция позволяет применять метод updated
три раза (работает неограниченное количество раз):
object BoardOps_patternMatch {
def updated(board: Board[_]) = {
board match {
case b: Board[x] => b.updated match {
case c: Board[y] => c.updated match {
case d: Board[z] => d.updated
}
}
}
}
}
Это потому, что как только вы связываете неизвестный тип с типом переменных x
, y
, z
компилятор вынужден выполнять дополнительную работу с логическим выводом и делает вывод, что x <: Board[_$?]
и т. д. К сожалению, он делает вывод только один шаг за раз, потому что, если он попытается вычислить наиболее точный тип, вычисление типа будет расходиться.
Верхние границы и универсальное количественное определение не совпадают
Обратите внимание, что ваш первый обходной путь работает только дважды:
object BoardOps_upperBound_once {
def updated(board: Board[_<:Board[_]]) = {
board.updated.updated // third .updated would not work
}
}
Следовательно, он не эквивалентен вашему второму обходному пути, который также работает неограниченное количество раз, например, с тремя вызовами updated
:
object BoardOps_genericT {
def updated[T <: Board[T]](board: T) : T = {
board.updated.updated.updated
}
}