17

给定以下定义:

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) 仅由于对 infix 的特殊处理而编译$,但是,它仍然没有解释 (1) 编译的原因。

问题:

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

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

3)有什么方法可以定义说`my_dollar`使用 GHC 扩展来做什么$,但也能够处理多态类型,所以(print . runST) `my_dollar` fourty_two编译?

编辑:建议的答案:

此外,以下无法编译:

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

这与 (1) 相同,除了不使用..

结果,似乎 GHC 对$和都有特殊规则.,但只有它们的中缀版本。

4

2 回答 2

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

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

  2. 什么都没有替代ImpredicativePolymorphism,因为 GHC 人员还没有看到任何用例,其中额外的程序员便利性超过了编译器错误的额外潜力。(我认为他们也不会认为这是令人信服的,尽管我当然不是权威。)
  3. 我们可以定义一个稍微少一点的多态性($$)

    {-# 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
    
于 2012-04-27T18:32:05.500 回答
0

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

考虑在每种情况下类型检查器必须做什么。(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 Int通过forall s. ST s tlet匹配t = Int, x = s。所以直接调用案例有效。但是,如果我们使用 会发生什么$

$有类型($) :: forall a b. (a -> b) -> a -> b。当我们解析aandb时,由于类型 for$没有像这样的任何显式类型作用域,因此x参数 offourty_two被提升到 for -so 类型的最外层($)作用域($) :: forall x t. (a = forall s. ST s t -> b = IO ()) -> (a = ST x t) -> IO ()。此时,它尝试匹配aand b,但失败了。

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

于 2012-04-27T06:46:40.967 回答