为什么绑定到"不受限制的"类型变量使变量刚性?

时间:2017-12-17 18:52:08

标签: haskell

我有这个函数来生成随机值列表并返回生成器(如果有人知道更好的方法,请随意将其留在评论中):

-- randomN :: (Random a, RandomGen g) => g -> Int -> ([a], g)
randomN gen n = (map fst rs, snd (last rs)) where
    rs = take n (pool gen)
    pool g = (r, g'):(pool g') where (r, g') = random g

我想用它来改组名单。我创建了这个函数:

shuffle gen stuff = (map snd $ sortOn fst $ zip rs stuff, gen') where
    rs :: [Int]
    (rs, gen') = randomN gen (length stuff)

我明白我需要明确地实例化rs,GHC不会神奇地想出我想如何对列表进行排序。

这是一个问题:我原来的尝试看起来像这样:

shuffle gen stuff = (map snd $ sortOn fst $ zip rs stuff, gen') where
    (rs, gen') = randomN gen (length stuff) :: ([Int], g)

但是由于以下错误而失败:

shuffle_minimal.hs:11:18: error:
    • Couldn't match type ‘t1’ with ‘g’
        because type variable ‘g’ would escape its scope
      This (rigid, skolem) type variable is bound by
        an expression type signature:
          ([Int], g)
        at shuffle_minimal.hs:11:18-57
      Expected type: ([Int], g)
        Actual type: ([Int], t1)
    • In the expression: randomN gen (length stuff) :: ([Int], g)
      In a pattern binding:
        (rs, gen') = randomN gen (length stuff) :: ([Int], g)
      In an equation for ‘shuffle’:
          shuffle gen stuff
            = (map snd $ sortOn fst $ zip rs stuff, gen')
            where
                (rs, gen') = randomN gen (length stuff) :: ([Int], g)
    • Relevant bindings include
        gen :: t1 (bound at shuffle_minimal.hs:10:9)
        shuffle :: t1 -> [b] -> ([b], t) (bound at shuffle_minimal.hs:10:1)

如果类型变量与" g"一样通用,那么为什么编译器无法将其与其他任何类型相匹配?

什么"因为类型变量'g'会逃避它的范围"?我试过阅读some other post,但我真的不明白: - (

谢谢!

2 个答案:

答案 0 :(得分:4)

因为g不一样。

您定义中的推断类型shuffle如下所示:

 shuffle :: (RandomGen t1) => t1 -> [a] -> [a]
 shuffle gen stuff = ...
      where 
          (res, gen') = ... :: (Int, g)

shuffle类型签名t1中的生成器类型和gen'签名g中的生成器类型与编译器的角度不同。您的代码中没有任何内容可以说它们属于同一类型。所以编译器抱怨。

第一个明显的解决方案是明确地给它们相同的名称:

 shuffle :: (RandomGen g) => g -> [a] -> [a]
 shuffle gen stuff = ...
      where 
          (res, gen') = ... :: (Int, g)

但是,这也行不通。由于一些复杂的历史原因,即使这样宣布,两个g类型仍然不会被视为相同的类型。

是的,这令人沮丧。因此,现代GHC提供了几个方便的扩展来修复它:ExplicitForAllScopedTypeVariables(启用后者自动启用前者)。

启用这些扩展后,您可以像这样修改您的功能:

 shuffle :: forall g a. (RandomGen g) => g -> [a] -> [a]
 shuffle gen stuff = ...
      where 
          (res, gen') = ... :: (Int, g)

签名中的forall(由ExplicitForAll扩展程序启用)非常重要:它会为类型变量打开一个新的范围并说明变量{{1} }和g是该范围内的两种新类型。范围大小是整个函数体,并且启用了a,在该范围中提及这些类型变量将意味着对这些相同类型的引用,而不是新类型。

答案 1 :(得分:3)

  

以下是问题:[...]

     

如果类型变量与" g"一样通用,那么为什么编译器无法将其与其他任何类型相匹配?

首先,请注意为shuffle提供类型注释会阻止此错误消息。实际上,这就是为什么大多数Haskellers强烈建议为所有顶级绑定添加类型注释(至少)。

如果没有明确的类型为GHC提供你的意图,GHC必须猜测 - 使用它的推理引擎来做。

shuffle gen stuff = ...

gen是什么类型的? GHC不知道,所以它生成一个新的类型变量t1来表示它的类型。这个变量并不严格,这意味着稍后GHC可能会选择将t1与其他类型统一起来。例如。如果您的代码包含gen == "hi",那么GHC将统一t1 ~ String

稍后我们会找到

randomN gen (length stuff) :: ([Int], g)

实际上意味着

randomN gen (length stuff) :: forall g. ([Int], g)

意味着此类表达式为([Int], String)([Int], Bool)以及([Int], Whatever)g上隐含的通用量化使其代表任意类型"。这里g是严格的:GHC只能选择一种类型 - 签名要求所有类型!

但是,表达式的类型为([Int], t1),因为它的第二对返回与gen相同类型的新生成器。这会使t1g统一 - 但这没有任何意义!实际上,t1代表单一类型,g代表"任意"键入:选择t1 ~ g会将g隐式修复为单个类型。或者换句话说,我们不能选择t1为"等于所有类型g" - 这将是无稽之谈。

GHC通过比较gt1的范围来防止这种错误的统一:由于非刚性t1具有更大的范围,因此无法与刚性g