Haskell排名两个多态性编译错误

时间:2012-04-27 06:29:52

标签: haskell polymorphism

鉴于以下定义:

import Control.Monad.ST
import Data.STRef

fourty_two = do
  x <- newSTRef (42::Int)
  readSTRef x

以下在GHC下编译:

main = (print . runST) fourty_two -- (1)

但这不是:

main = (print . runST) $ fourty_two -- (2)

但是当 bdonlan 在评论中指出时,这会编译:

main = ((print . runST) $) fourty_two -- (3)

但是,这不会编译

main = (($) (print . runST)) fourty_two -- (4)

这似乎表明(3)仅由于中缀$的特殊处理而编译,但是,它仍然没有解释为什么(1)编译。

问题:

1)我已经阅读了以下两个问题(firstsecond),并且我被引导相信$只能用单态类型进行实例化。但我同样假设.只能用单态类型实例化,结果同样会失败。 为什么第一个代码成功但第二个代码没有? (例如,对于第一种情况,GHC是否有特殊规则,它不适用于第二种情况?)

2)是否有编译第二个代码的当前GHC扩展? (也许ImpredicativePolymorphism在某些时候做到了这一点,但它似乎已被弃用,有什么东西取而代之吗?)

3)有没有办法定义说`my_dollar`使用GHC扩展来执行$所做的事情,但也能够处理多态类型,所以(print . runST) `my_dollar` fourty_two编译?

编辑:建议答案:

此外,以下内容无法编译:

main = ((.) print runST) fourty_two -- (5)

除了不使用.的中缀版本外,这与(1)相同。

因此,似乎GHC对$.都有特殊规则,但只有中缀版本。

2 个答案:

答案 0 :(得分:6)

  1. 我不确定我理解为什么第二个不起作用。我们可以看一下print . runST的类型,并观察它是多态的,因此责任不在于(.)。我怀疑GHC对中缀($)的特殊规则是不够的。如果您将此片段作为其跟踪器上的错误提议,SPJ和朋友可能会重新检查它。

    至于为什么第三个例子有效,那就是因为((print . runST) $)的类型再次具有足够的多态性;实际上,它等于print . runST

  2. 的类型
  3. 没有任何内容替换ImpredicativePolymorphism,因为GHC人员没有看到任何使用情况,额外的程序员方便性超过了编译器错误的额外可能性。 (我认为他们也不认为这很引人注目,但我当然不是权威。)
  4. 我们可以定义稍微更少的多态($$)

    {-# LANGUAGE RankNTypes #-}
    infixl 0 $$
    ($$) :: ((forall s. f s a) -> b) -> ((forall s. f s a) -> b)
    f $$ x = f x
    

    然后您的示例类型检查可以使用此新运算符:

    *Main> (print . runST) $$ fourty_two
    42
    

答案 1 :(得分:0)

我不能在这个问题上说太多权威,但这是我认为可能发生的事情:

考虑一下typechecker在每种情况下必须做的事情。 (print . runST)的类型为Show b => (forall s. ST s t) -> IO ()fourty_two的类型为ST x Int

forall这里是一个存在类型限定符 - 这意味着传入的参数必须是s上的通用。也就是说,您必须传入支持s的任何值的多态类型。如果未明确声明forall,则Haskell将其置于类型定义的最外层。这意味着fourty_two :: forall x. ST x Int(print . runST) :: forall t. Show t => (forall s. ST s t) -> IO ()

现在,我们可以通过forall x. ST x Intforall s. ST s tt = Int, x = s匹配。所以直接调用案例有效。但是,如果我们使用$会发生什么?

$的类型为($) :: forall a b. (a -> b) -> a -> b。当我们解析ab时,由于$的类型没有这样的任何显式类型范围,x的{​​{1}}参数会被解除超出fourty_two类型的最外层范围 - 所以($)。此时,它会尝试匹配($) :: forall x t. (a = forall s. ST s t -> b = IO ()) -> (a = ST x t) -> IO ()a,然后失败。

如果您改为编写b,则编译器首先解析((print . runST) $) fourty_two的类型。它将($)的类型解析为((print . runST $);请注意,由于forall t. (a = forall s. ST s t -> b = IO ()) -> a -> b的第二次出现不受约束,我们没有那个讨厌的类型变量泄漏到最外层范围!所以匹配成功,部分应用了函数,表达式的整体类型为a,它正好在我们开始的地方,所以它成功了。