为什么不使用forall(RankNTypes用法)?

Pet*_*all 8 haskell ghc

我不太熟悉forall,但最近读过这个问题:Haskell/GHC中的`forall`关键字有什么作用?

其中一个答案是这个例子:

 {-# LANGUAGE RankNTypes #-}
 liftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b)
 liftTup liftFunc (t, v) = (liftFunc t, liftFunc v)
Run Code Online (Sandbox Code Playgroud)

解释很好,我理解forall这里做了什么.但我想知道,这是不是默认行为的特殊原因.有没有时间会不利?

编辑:我的意思是,有没有理由为什么默认情况下不能插入forall?

ehi*_*ird 16

好吧,它不是Haskell 2010标准的一部分,因此它默认情况下不启用,而是作为语言扩展提供.至于为什么它不在标准中,rank-n类型比标准Haskell具有的普通rank-1类型更难实现; 它们也经常不需要它们,所以委员会可能决定不用语言和实现简单的原因来打扰它们.

当然,这并不意味着rank-n类型没用; 它们非常如此,如果没有它们,我们就不会拥有像STmonad这样的有价值的工具(它提供了高效的本地可变状态 - 就像IO你所能做的就是使用IORefs).但它们确实为语言增加了相当多的复杂性,并且在应用看似良性的代码转换时会引起奇怪的行为.例如,一些rank-n类型的检查器将允许runST (do { ... })但拒绝runST $ do { ... },即使这两个表达式总是等效而没有rank-n类型.请参阅此SO问题,了解它可能导致的意外(有时令人讨厌)行为的示例.

如果像sepp2k那样问,那么你要问为什么forall必须明确地添加到类型签名以获得更高的通用性,问题(forall x. x -> f x) -> (a, b) -> (f a, f b)是实际上是一种比限制性更强的类型(x -> f x) -> (a, b) -> (f a, f b).对于后者,您可以传入表单的任何函数x -> f x(对于任何fx),但对于前者,您传入的函数必须适用于所有 x.因此,例如,类型的函数String -> IO String将是第二个函数的允许参数,但不是第一个函数; 它必须有类型a -> IO a.如果后者自动转变为前者,那将是相当混乱的!它们是两种截然不同的类型.

隐含的foralls明确表示可能更有意义:

forall f x a b. (x -> f x)           -> (a, b) -> (f a, f b)
forall f a b.   (forall x. x -> f x) -> (a, b) -> (f a, f b)
Run Code Online (Sandbox Code Playgroud)


Joh*_*n L 7

我怀疑默认情况下不启用更高级别的类型,因为它们使类型推断不可判定.这也是为什么,即使启用了扩展,您也需要使用forall关键字来获得更高级别的类型 - GHC假设所有类型都是rank-1,除非另有明确说明,以便尽可能多地推断类型信息.

换句话说,没有推断更高级别类型的一般方法(forall x. x -> f x) -> (a,b) -> (f a, f b),因此获得该类型的唯一方法是通过显式类型签名.

编辑:根据Vitus上面的评论,rank-2类型推断是可判定的,但更高级别的多态性不是.因此,这种类型的签名在技术上是可推断的(尽管算法更复杂).启用rank-2多态类型推断的额外复杂性是否值得讨论是值得商榷的......