Аналогия между `F[_ <: A] <: B` на уровне типа и`f: A => B` на уровне значения

Предполагая F[_ <: A] <: B как типовой аналог f: A => B, позволять [F[_ <: Int] <: List[Int], A <: Int], тогда не следует вводить приложение F[A] уступить, когда A = Int, так f(List(42)) должен компилироваться в следующем случае

      $ scala3-repl
scala> def f[F[_ <: Int] <: List[Int], A <: Int](as: F[A]) = as
def f[F[_$1] <: List[Int], A <: Int](as: F[A]): F[A]

scala> f(List(42))
1 |f(List(42))
  |  ^^^^^^^^
  |Found:    List[Int]
  |Required: F[A]
  |
  |where:    A is a type variable with constraint <: Int
  |          F is a type variable with constraint <: [_$1 <: Int] =>> List[Int]

Применение сообщения об ошибке путем явного предоставления параметров типа заставляет его работать

      scala> f[[_ <: Int] =>> List[Int], Int](List(42))
val res0: List[Int] = List(42)

Где прерывается аналогия? Где моя ментальная модель рассмотрения F[_ <: Int] <: List[Int] как функция уровня типа из Int к List[Int] неправильный?

3 ответа

Во-первых, что касается вывода лямбда-выражений типа, я думаю, что вывод типа не дойдет до такой степени, чтобы придумать лямбды-типы просто для удовлетворения ограничений, иначе интуитивно казалось бы, что все, по сути, могло бы проверять тип с помощью некоторой запутанной лямбда-типа, и это не было бы полезно при обнаружении ошибок типа.

Что касается того, почему не удается скомпилировать (и, следовательно, не удается сделать вывод), нам нужно будет обратиться к определения Правиламподтипов типа lambdas:

Предположим, что лямбды двух типов

       type TL1  =  [X >: L1 <: U1] =>> R1
type TL2  =  [X >: L2 <: U2] =>> R2

потом TL1 <: TL2, если

  • интервал типа L2..U2 содержится в интервале типов L1..U1 (т.е. L1 <: L2 а также U2 <: U1),
  • R1 <: R2

Также обратите внимание, что:

Конструктор частично применяемого типа, например Listсчитается эквивалентным его разложению в эту. Т.е., List = [X] =>> List[X]. Это позволяет сравнивать конструкторы типов с лямбда-выражениями типов.

Это означает, что все они будут компилироваться:

      f[[_ <: Int] =>> List[Int], Int](List(42)) //author's compiler example
f[[_] =>> List[Int], Int](List(42)) //input type bounds can be wider, output stays the same
f[[_] =>> List[42], Int](List(42)) //input wider, output narrower
f[[x <: Int] =>> List[x], Int](List(42))//input same, output narrower

И всего этого не будет:

      f[[x] =>> List[x], Int](List(42)) //input type bounds can be wider but it will also make the output wider
f[List, Int](List(42)) //equivalent to preceding case
f[[_ <: 42] =>> List[Int], Int](List(42)) //input type bounds cannot be narrower

В случае:

      def f[F[x] <: List[x], A <: Int](as: F[A]) = as

Если вы посмотрите на него с точки зрения лямбда того же типа f[[x] =>> List[x], Int](List(42)) должен работать, и поэтому f[List, Int](List(42)) также будет компилироваться (и будет предполагаться).

Не уверен, откуда у вас начальное предположение. F[_ <: Int] означает "конструктор типа, для которого a <: Int всегда держит. List не является конструктором такого типа (он допускает любой тип), поэтому вы не можете вызвать функцию с помощью F = List.

Сравните это с чем-то вроде:

      def f[F[a] <: List[a], A <: Int](as: F[A]) = as

Это говорит о том, что F - конструктор типа, такой что F[a] расширяет List[a] для всех типов a.

На основе умнее

      ?F := List

который расширяется до

      ?F = [X] =>> List[X] 

не является допустимым решением ограничения

      ?F <: [X <: Int] =>> List[Int]

потому что мы проверяем

      List[X] <: List[Int]

для произвольного X.

Таким образом, похоже, что ключ в том, что когда дело доходит до конструкторов типов с проверкой типов, компилятор будет использовать только информацию из определения конструктора типа, а не дополнительную информацию из предложения типа. Так как определяется как

      trait List[+A]

где A произвольно, то есть A >: Nothing <: Any, то компилятор просто использует эту информацию, а не X <: Intчто он потенциально может быть взят из более широкого предложения о типе. Если конструктор типа List вместо этого был определен как

      trait List[+A <: Int]

например, введите проверку. Что касается того, почему он не выводит более конкретную лямбду типа, это, похоже, связано с тем фактом, что унификация конструктора типов в целом неразрешима.

Унификация высшего порядка в целом неразрешима, поэтому компилятор должен ограничить ее разумным подмножеством

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