所以我最近才了解并开始使用TypeApplications
,并且想知道我们如何通常知道我们分配了什么类型的变量。我找到的关于TypeApplications
的文档提到:
使用什么顺序来实例化类型变量?
类型变量出现在forall中的从左到右顺序。这是在类型变量级别完成实例化时发生的最合乎逻辑的顺序。嵌套 forall 的工作方式略有不同,但在具有多个变量的单个 forall 位置,将发生从左到右的顺序。(嵌套的森林见下文(。
但是,我还没有发现提到隐式 foralls 中类型变量的顺序是如何确定的。我尝试用-fprint-explicit-foralls
查看不同的示例,看看是否有一个简单的模式,但是在不同版本的ghci中得到了不同的结果。:/
在 ghci 版本 8.0.2 中,我得到:
> :t (,,)
(,,) :: forall {c} {b} {a}. a -> b -> c -> (a, b, c)
而在 ghci 版本 8.4.3 中,我得到:
> :t (,,)
(,,) :: forall {a} {b} {c}. a -> b -> c -> (a, b, c)
再说一次,也许这只是 foralls 在 8.0.2 中打印方式中的一个错误,因为否则类型应用程序似乎使用 forall 的变量从右到左完成,与文档所说的相反:
> :t (,,) @Bool
(,,) @Bool :: forall {c} {b}. Bool -> b -> c -> (Bool, b, c)
那么,类型变量是否总是按照它们在类型主体中从左到右出现的顺序(包括约束(放在隐式 foralls 中?
TL;DR:类型变量顺序由第一次从左到右的遭遇决定。 如有疑问,请使用:type +v
.
不要使用:type
在这里使用:type
具有误导性。:type
推断整个表达式的类型。所以当你写:t (,)
时,类型检查器会查看
(,) :: forall a b. a -> b -> (a, b)
并使用新类型变量实例化所有 forall
(,) :: a1 -> b1 -> (a1, b1)
如果您要申请(,)
,这是必要的。唉,你没有,所以类型推断几乎完成了,它推广到所有自由变量,例如,你得到
(,) :: forall {b} {a}. a -> b -> (a, b)
此步骤不保证自由变量的顺序,并且编译器可以自由更改。
另请注意,它写的是forall {a}
而不是forall a
,这意味着您不能在此处使用可见类型应用程序。
使用:type +v
但是,当然您可以使用(,) @Bool
- 但是在这里,类型检查器以不同的方式处理第一个表达式,并且不执行此实例化/泛化步骤。
你也可以在GHCi中获得这种行为 -+v
传递给:type
:
:type +v (,)
(,) :: forall a b. a -> b -> (a, b)
:type +v (,) @Bool
(,) @Bool :: forall b. Bool -> b -> (Bool, b)
看,没有关于类型变量的{…}
!
这个订单从何而来?
GHC 用户指南中有关可见类型应用的部分指出:
如果标识符的类型签名不包含显式 forall,则类型变量参数按变量在类型中出现的从左到右顺序显示。所以, foo :: monad m => a b -> m (a c( 将有其类型变量排序为 m, a, b, c。
这仅适用于具有显式类型签名的内容。推断类型中的变量没有保证的顺序,但您也不能将具有推断类型的表达式与VisibleTypeApplication
一起使用。