Аргументы типа логического вывода в 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 функция.

Другие вопросы по тегам