Аргументы типа логического вывода в Scala - границы типа, выводящие "Nothing"
Я пытаюсь написать простую монаду запроса и у меня проблемы с получением правильных аннотаций общего типа.
Моя первая попытка пошла следующим образом (значительно упрощена для краткости)
case class Person( val name: String )
abstract class Schema[T]
object People extends Schema[Person]
case class Query[U <: Schema[T], T]( schema: U ) { <---- Type signature
def results: Seq[T] = ...
def where( f: U => Operation ) = ...
}
class TypeText extends Application {
val query = Query( People ) <---- Type inference fails
}
Компилятору это не понравилось, так как он не мог вывести тип 'T'.
ошибка: выводимые аргументы типа [People.type,Nothing] не соответствуют границам параметров типа применения метода [U <: Schema [T], T]
Экспериментируя, я обнаружил, что использование границ вида работает как ожидалось
case class Query[U <% Schema[T], T]( schema: U ) {
(Обратите внимание на использование вида<<) вместо типа <:
Однако в моем ограниченном понимании системы типов, так как я ожидаю фактический подкласс (а не только конвертируемость) схемы [T], я бы предположил, что привязка к типу "<:" является правильной границей, используемой здесь?
Если это так, чего мне не хватает - как дать компилятору достаточно подсказок для правильного вывода T при использовании границ типа вместо границ вида?
4 ответа
Для того, чтобы закодировать отношения между двумя параметрами типа, вы можете использовать что-то вроде
case class Query[U, T](schema: U)(implicit ev: U <:< Schema[T]) { ... }
См. §4.3 и §4.4 спецификации языка Scala для получения дополнительной информации.
Это не полностью статистический ответ (по крайней мере, для меня), так как я должен признать, что не могу точно сказать, где и почему вывод здесь терпит неудачу. У меня есть только некоторые нечеткие интуиции об этом. Проблема связана с тем, что компилятору приходится выводить два параметра типа одновременно. Что касается того, почему изменение типа, связанного с границей вида, исправляет компиляцию, я понимаю, что теперь есть два списка параметров, и что в результате мы теперь имеем две последовательные фазы вывода типов вместо двух выводов одновременно. Действительно, следующее:
case class Query[U <% Schema[T], T]( schema: U )
такой же как:
case class Query[U, T]( schema: U )( implicit conv: U => Schema[T] )
Первый список параметров управляет выводом U
, а затем второй (обратите внимание, что U
теперь знаю) буду делать вывод T
,
В случае выражения Query( People )
параметр People
будет приводить тип вывода, чтобы установить U
в People.type
, Затем компилятор будет искать неявное преобразование из People.type
в Schema[T]
, чтобы перейти во второй список параметров. Единственное в области - это (тривиальное) преобразование из People.type
в Schema[Person]
, заставляя логически выводить, что T = Person
,
Чтобы исправить компиляцию, не прибегая к границам вида, вы можете заменить параметр type T
с абстрактным типом:
case class Person( val name: String )
sealed trait Schema {
type T
}
abstract class SchemaImpl[_T] extends Schema {
type T = _T
}
object People extends SchemaImpl[Person]
case class Query[U <: Schema]( schema: U ) {
def results: Seq[schema.T] = ???
}
class TypeText extends Application {
val query = Query( People )
}
ОБНОВЛЕНИЕ:
@ Аарона Новструпа: насколько мне известно, ваш ответ неверен (обновление до обновления: оригинальный ответ от Аарона утверждал, что Query
декларация была эквивалентна case class Query[U <: Schema[X], T](schema: U)
).
case class Query[U <: Schema[X], T](schema: U)
даже не компилируется. Допустим, что вы имели в виду
case class Query[U <: Schema[_], T](schema: U)
(который компилируется), в REPL легко проверить, что это не то же самое.
Действительно, следующее компилируется нормально:
case class Query[U <: Schema[_], T](schema: U)
type MyQuery = Query[Schema[String], Int]
Хотя следующее не делает:
case class Query[U <: Schema[T], T](schema: U)
type MyQuery = Query[Schema[String], Int]
Отсюда и доказываем разницу. Ошибка:
<console>:10: error: type arguments [Schema[String],Int] do not conform to class Query's type parameter bounds [U <: Schema[T],T]
type MyQuery = Query[Schema[String], Int]
Что ясно показывает, что первый и второй случаи T
обозначим один и тот же тип, и у нас есть связь между двумя параметрами типа.
У меня такая же проблема. Следующее работало для меня:
case class Query[U <: Schema[T], T]( schema: U with Schema[T] ) {
...
}
Я всегда обнаруживал, что при использовании двух идентификаторов типов в классе / функции система вывода типов работает не так, как ожидалось, и вы должны быть явно указаны так:
val query = Query[People.type, Person]( People )
Если вы изменили свой Query
заявление к этому:
case class Query[U <: Schema[_]( schema: U )
Вы сможете сделать это:
val query = Query( People )
Но тогда вы не знали бы основной тип Schema
поставляется и не сможет должным образом реализовать results
функция.