简体   繁体   中英

Analogy between `F[_ <: A] <: B` at type-level and `f: A => B` at value-level

Assuming F[_ <: A] <: B as type-level analog of f: A => B , let [F[_ <: Int] <: List[Int], A <: Int] , then should't type application F[A] yield List[Int] when A = Int , so f(List(42)) should compile in the following case

$ 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]

Applying the error message by explicitly providing type parameters makes it work

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

Where does the analogy break? Where is my mental model of considering F[_ <: Int] <: List[Int] as a type-level function from Int to List[Int] wrong?

Firstly regarding inferring type lambdas, I think that type inference would not go to the extent of coming up with type lambdas just to satisfy the constraints otherwise intuitively it would seem like everything would essentially be able typecheck with some convoluted type lambda and that would not be useful in picking up type errors.

As to why f[List, Int](List(42)) fails to compile (and thus fails to get inferred) we would need to refer to the Subtyping Rules of type lambdas:

Assume two type lambdas

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

Then TL1 <: TL2 , if

  • the type interval L2..U2 is contained in the type interval L1..U1 (ie L1 <: L2 and U2 <: U1 ),
  • R1 <: R2

Also note that:

A partially applied type constructor such as List is assumed to be equivalent to its eta expansion. Ie, List = [X] =>> List[X] . This allows type constructors to be compared with type lambdas.

Which means all of these will compile:

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

And all of these will not:

f[[x] =>> List[x], Int](List(42)) //input type bounds can be wider but in this case 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

In the case of:

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

If you view it from the same type lambda perspective f[[x] =>> List[x], Int](List(42)) should work, and thus f[List, Int](List(42)) would also compile (and be inferred).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM