在 GitHub 上编辑此页面

类型 Lambda - 更多细节

语法

Type            ::=  ... |  TypeParamClause ‘=>>’ Type
TypeParamClause ::=  ‘[’ TypeParam {‘,’ TypeParam} ‘]’
TypeParam       ::=  {Annotation} (id [HkTypeParamClause] | ‘_’) TypeBounds
TypeBounds      ::=  [‘>:’ Type] [‘<:’ Type]

类型检查

[X] =>> F[X] 这样的类型 Lambda 定义了一个从类型到类型的函数。参数可以带有边界。如果参数有边界,例如 [X >: L <: U] =>> F[X],则会检查参数传递给参数是否符合边界 LU。只有上界 U 可以是 F 绑定的,即 X 可以出现在其中。

子类型规则

假设两个类型 Lambda

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

那么 TL1 <: TL2,如果

  • 类型区间 L2..U2 包含在类型区间 L1..U1 中(即 L1 <: L2U2 <: U1),
  • R1 <: R2

这里我们依赖于 alpha 重命名 来匹配两个绑定类型 X

部分应用的类型构造函数,例如 List,被认为等同于其 eta 展开。即,List = [X] =>> List[X]。这允许类型构造函数与类型 lambda 进行比较。

与参数化类型定义的关系

参数化类型定义

type T[X] = R

被视为一个简写,对应于一个非参数化定义,其右侧为类型 lambda

type T = [X] =>> R

如果类型定义带有 +- 方差注解,则会检查类型 lambda 是否满足方差注解。例如,

type F2[A, +B] = A => B

展开为

type F2 = [A, B] =>> A => B

同时检查参数 BA => B 中是否协变出现。

参数化抽象类型

type T[X] >: L <: U

被视为一个简写,对应于一个非参数化抽象类型,其边界为类型 lambda。

type T >: ([X] =>> L) <: ([X] =>> U)

但是,如果 LNothing,则它不会被参数化,因为 Nothing 被视为所有种类的底类型。例如,

type T[X] <: X => X

展开为

type T >: Nothing <: ([X] =>> X => X)

而不是

type T >: ([X] =>> Nothing) <: ([X] =>> X => X)

相同的展开也适用于类型参数。例如,

[F[X] <: Coll[X]]

被视为一个简写,对应于

[F >: Nothing <: [X] =>> Coll[X]]

抽象类型和不透明类型别名会记住它们创建时的方差。因此,类型

type F2[-A, +B]

已知在 A 中逆变,在 B 中协变,并且只能用满足这些约束的类型实例化。同样地

opaque type O[X] = List[X]

O 已知是不变的(而不是协变,因为其右侧会暗示)。另一方面,透明别名

type O2[X] = List[X]

将被视为协变,X 在其右侧协变使用。

注意:Nothing 视为通用底类型的决定是暂时的,可能会在进一步讨论后更改。

注意: Scala 2 和 3 的区别在于 Scala 2 还将 Any 视为通用顶类型。Scala 3 中没有这样做。另请参阅有关 类型多态 的讨论。

柯里化类型参数

类型 lambda 的主体可以再次是类型 lambda。示例

type TL = [X] =>> [Y] =>> (X, Y)

目前,没有为推断此类柯里化类型 lambda 的类型参数做出特殊规定。这留待将来工作。