为什么更高级别的类型在 Haskell 中如此脆弱

Why are higher rank types so fragile in Haskell

我在摆弄 runST 函数。其类型为 (forall s. ST s a) -> a 并且似乎试图以任何不直接应用的方式使用它而没有任何间接会以非常讨厌的方式破坏它。

runST :: (forall s. ST s a) -> a
const :: a -> b -> a

所以用 const 中的 a 代替 forall s. ST s a 你应该得到 const runST

的类型
const runST :: b -> (forall s. ST s a) -> a

但 GHC 说它无法将 a(forall s. ST s a) -> a 匹配,但由于 a 字面意思是 forall a. a ,我不满足每种类型看看有什么无效的。

事实证明,使用 \_ -> runST 实际上工作得很好并且给出了正确的类型。

一旦我 constST 具有正确的类型,我想看看我是否可以 uncurry 它,但不出所料,它也会中断。但它似乎真的不应该,虽然诚然这个案例似乎不如前一个明显:

constST :: b -> (forall s. ST s a) -> a
uncurry :: (a -> b -> c) -> (a, b) -> c

那么肯定 uncurry 中的 a 可以替换为 constST 中的 b 并且 uncurry 中的 b 可以可以用 constST 中的 (forall s. ST s a) 替换,uncurry 中的 a 可以用 constST 中的 c 替换。这给了我们:

uncurry constST :: (b, forall s. ST s a) -> a

诚然,这种类型是非谓语式的,我知道这很成问题。但从技术上讲,Just mempty 在不移动隐式 forall 量化的情况下直接替换时也是不可预测的。

Just :: a -> Maybe a
mempty :: forall a. Monoid a => a
Just mempty :: Maybe (forall a. Monoid a => a)

但是forall自动上浮给你:

Just mempty :: forall a. Monoid a => Maybe a

现在,如果我们对 uncurry constST 做同样的事情,我们应该得到明智的,据我所知,正确的类型是:

uncurry constST :: (forall s. (b, ST s a)) -> a

哪个级别更高但不是必然的。

有人可以向我解释为什么上面的 none 实际上可以与 GHC 8 一起使用吗?在一般情况下,是否有更基本的东西使上面的内容变得非常复杂?因为如果不是这样的话,如果只是为了 runST.

而摆脱烦人的 $ 的特殊外壳,那么进行上述工作似乎真的很好

附带说明一下,我们可以让 ImpredicativeTypes 正常工作,而不是所有 forall 浮动。它正确地将 Just mempty 的类型推断为 Maybe (forall a. Monoid a => a) 但似乎实际使用它并不那么容易。我听说谓词类型推断并不是真正可行的,但它是否可以以某种方式将类型推断限制为谓词类型,除非您提供类型签名来指示其他情况。类似于 MonoLocalBinds 为了类型推断而默认使局部绑定单态化。

为了能够编写 const runST,您需要激活 Impredicative 类型扩展(在 GHCi 上::set -XImpredicativeTypes)。

您已经回答了自己的问题:

... by substituting a in const for forall s. ST s a ...

这是 预测多态性 的定义 - 使用 polytype 实例化类型变量的能力,这是(松散地)具有 [=18 的类型=] 类型最左边的量词。

来自关于该主题的 GHC trac 页面:

GHC does not (yet) support impredicative polymorphism

还有

We've made various attempts to support impredicativity, so there is a flag -XImpredicativeTypes. But it doesn't work, and is absolutely unsupported. If you use it, you are on your own; I make no promises about what will happen.

所以不要使用 ImpredictiveTypes - 它没有帮助。


现在是血淋淋的细节 - 为什么所有的具体示例都像它们那样工作?

您注意到在表达式 Just mempty 中,未推断出不可预测类型 Maybe (forall a. Monoid a => a);相反,forall 是 'floated out'。您还注意到,对 uncurry constST 执行相同的过程会给出一个“等级更高但不具有约束力”的类型。 GHC user guide 对更高级别的类型有这样的说法:

In general, type inference for arbitrary-rank types is undecidable. ...

For a lambda-bound or case-bound variable, x, either the programmer provides an explicit polymorphic type for x, or GHC’s type inference will assume that x’s type has no foralls in it.

所以你真的必须帮助它很多,这通常会排除一起使用高阶函数(注意上面说 nothing 关于任意应用程序,只关于绑定变量- 并且 uncurry constST 没有绑定变量!)。 Just mempty 的正确类型是等级 1,因此推断它没有问题,有或没有额外的类型签名。

例如,您可以这样编写 (forall s. (b, ST s a)) -> a 函数(至少在 GHC 8.0.1 上):

constST' :: forall a b . (forall s . (b, ST s a)) -> a
constST' z = runST z' 
  where z' :: forall s . ST s a
        z' = snd z

还要注意,您甚至不能对这对进行模式匹配,因为这会立即实例化绑定的 b 类型 var:

constST' :: forall a b . (forall s . (b, ST s a)) -> a
constST' (a,b) = _res 

使用打字孔,你得到:

* Found hole: _res :: a
  Where: `a' is a rigid type variable bound by
           the type signature for:
             constST' :: forall a b. (forall s. (b, ST s a)) -> a
* In the expression: _res
  In an equation for constST': constST' (a, b) = _res
* Relevant bindings include
    b :: ST s0 a 
    a :: b 

请注意 b 的类型是 ST s0 a 对于某些 新类型变量 s0,不是必需的 forall s . ST s a runST。无法取回旧类型。


解决此类问题的最简单方法可能是定义一个 newtype,正如 GHC trac 页面所建议的:

newtype RunnableST a = RST (forall s . ST s a)

rrunST :: RunnableST a -> a 
rrunST (RST a) = runST a 

并将准备好 运行 的 ST 操作存储在此容器中:

doSomething :: RunnableST X
doSomething = RST $ do ...