我不太熟悉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(对于任何f和x),但对于前者,您传入的函数必须适用于所有 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)
我怀疑默认情况下不启用更高级别的类型,因为它们使类型推断不可判定.这也是为什么,即使启用了扩展,您也需要使用forall关键字来获得更高级别的类型 - GHC假设所有类型都是rank-1,除非另有明确说明,以便尽可能多地推断类型信息.
换句话说,没有推断更高级别类型的一般方法(forall x. x -> f x) -> (a,b) -> (f a, f b),因此获得该类型的唯一方法是通过显式类型签名.
编辑:根据Vitus上面的评论,rank-2类型推断是可判定的,但更高级别的多态性不是.因此,这种类型的签名在技术上是可推断的(尽管算法更复杂).启用rank-2多态类型推断的额外复杂性是否值得讨论是值得商榷的......