[F <: List[A], A] 和 [F[_] <: List[A], A] 之间的推理差异
Difference in inference between [F <: List[A], A] and [F[_] <: List[A], A]
考虑以下两个类型参数子句中类型参数A
对类型构造函数的推断差异
$ scala3-repl
scala> def f[F <: List[A], A](as: F) = as
def f[F <: List[A], A](as: F): F
scala> f(List(42))
val res0: List[Int] = List(42)
scala> def f[F[_] <: List[A], A](as: F[A]) = as
def f[F[_] <: List[A], A](as: F[A]): F[A]
scala> f(List(42))
val res1: List[Any] = List(42)
为什么类型参数 A
到类型构造函数 F
在第二种情况下被推断为 Any
?
根据我对你在第二种情况下的定义的解释,F[_]
是一个 List
类型的构造函数,但是 List[A]
必须是任何列表的上限 F[_]
可以构造,所以 A
必须是 Any
.
可能你想要的是这个:
def f[F[_] <: List[_], A](as: F[A]) = as
或
def f[F[x] <: List[x], A](as: F[A]) = as
特别是对于x
需要固定为多个约束参数的情况(示例请参考下面的@user评论)
在第一种情况下 F
是一个具体类型,所以 List[A]
不是所有列表的上限,而是仅在列表 F
上,所以 A
不必是 Any
并且最窄的可推断类型是 Int
.
不是一个完整的答案,只是一些值得深思的问题:我试图构建一个反例,但在假设 A
将被推断为最窄的类型。不过,也许您会觉得它很有趣。
这里是一个具有类似约束的函数 h
,但我们采用稍微不同的类型构造函数而不是 List
。
主要思想是Cc
有两个独立的类型参数:
- 首先是
F[_]
中_
的意思
- 第二个是在
<: Lst[A]
-constraint 中与A
交互的那个
请注意,如果 A
被推断为最窄类型 (Nothing
),则不会编译:
(run in 3.0.0-RC2)
scala> trait Lst[+X]
// defined trait Lst
scala> case class Cc[+I, +X](i: I) extends Lst[X]
// defined case class Cc
scala> type T[+I] = Cc[I, Nothing]
// defined alias type T[+I] = Cc[I, Nothing]
scala> def h[F[_] <: Lst[A], A](as: F[A]) = as
def h[F[_] <: Lst[A], A](as: F[A]): F[A]
scala> val xs: T[Int] = Cc(42)
val xs: T[Int] = Cc(42)
scala> h(xs)
val res9: Cc[Int, Nothing] = Cc(42)
如果 A
被推断为满足 <: Lst[A]
约束的最窄可能类型,那么 A
将是 Nothing
,并且参数必须是输入T[Nothing] = Cc[Nothing, Nothing]
,无人居住
我认为这很有趣,但我不明白为什么如果不编译它实际上会很糟糕。
考虑以下两个类型参数子句中类型参数A
对类型构造函数的推断差异
$ scala3-repl
scala> def f[F <: List[A], A](as: F) = as
def f[F <: List[A], A](as: F): F
scala> f(List(42))
val res0: List[Int] = List(42)
scala> def f[F[_] <: List[A], A](as: F[A]) = as
def f[F[_] <: List[A], A](as: F[A]): F[A]
scala> f(List(42))
val res1: List[Any] = List(42)
为什么类型参数 A
到类型构造函数 F
在第二种情况下被推断为 Any
?
根据我对你在第二种情况下的定义的解释,F[_]
是一个 List
类型的构造函数,但是 List[A]
必须是任何列表的上限 F[_]
可以构造,所以 A
必须是 Any
.
可能你想要的是这个:
def f[F[_] <: List[_], A](as: F[A]) = as
或
def f[F[x] <: List[x], A](as: F[A]) = as
特别是对于x
需要固定为多个约束参数的情况(示例请参考下面的@user评论)
在第一种情况下 F
是一个具体类型,所以 List[A]
不是所有列表的上限,而是仅在列表 F
上,所以 A
不必是 Any
并且最窄的可推断类型是 Int
.
不是一个完整的答案,只是一些值得深思的问题:我试图构建一个反例,但在假设 A
将被推断为最窄的类型。不过,也许您会觉得它很有趣。
这里是一个具有类似约束的函数 h
,但我们采用稍微不同的类型构造函数而不是 List
。
主要思想是Cc
有两个独立的类型参数:
- 首先是
F[_]
中_
的意思 - 第二个是在
<: Lst[A]
-constraint 中与
A
交互的那个
请注意,如果 A
被推断为最窄类型 (Nothing
),则不会编译:
(run in 3.0.0-RC2)
scala> trait Lst[+X]
// defined trait Lst
scala> case class Cc[+I, +X](i: I) extends Lst[X]
// defined case class Cc
scala> type T[+I] = Cc[I, Nothing]
// defined alias type T[+I] = Cc[I, Nothing]
scala> def h[F[_] <: Lst[A], A](as: F[A]) = as
def h[F[_] <: Lst[A], A](as: F[A]): F[A]
scala> val xs: T[Int] = Cc(42)
val xs: T[Int] = Cc(42)
scala> h(xs)
val res9: Cc[Int, Nothing] = Cc(42)
如果 A
被推断为满足 <: Lst[A]
约束的最窄可能类型,那么 A
将是 Nothing
,并且参数必须是输入T[Nothing] = Cc[Nothing, Nothing]
,无人居住
我认为这很有趣,但我不明白为什么如果不编译它实际上会很糟糕。