Аналогия между `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]
например, введите проверку. Что касается того, почему он не выводит более конкретную лямбду типа, это, похоже, связано с тем фактом, что унификация конструктора типов в целом неразрешима.
Унификация высшего порядка в целом неразрешима, поэтому компилятор должен ограничить ее разумным подмножеством