我有这个函数来生成随机值列表并返回生成器(如果有人知道更好的方法,请随意将其留在评论中):
-- 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,但我真的不明白: - (
谢谢!
答案 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提供了几个方便的扩展来修复它:ExplicitForAll
和ScopedTypeVariables
(启用后者自动启用前者)。
启用这些扩展后,您可以像这样修改您的功能:
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
相同类型的新生成器。这会使t1
与g
统一 - 但这没有任何意义!实际上,t1
代表单一类型,g
代表"任意"键入:选择t1 ~ g
会将g
隐式修复为单个类型。或者换句话说,我们不能选择t1
为"等于所有类型g
" - 这将是无稽之谈。
GHC通过比较g
和t1
的范围来防止这种错误的统一:由于非刚性t1
具有更大的范围,因此无法与刚性g
。