Haskell:实现“randoms”(a.k.a.,Ambiguous type variable)

时间:2011-12-26 20:29:13

标签: haskell types ghci type-variables

我正在阅读LYAH,在第9章中,我发现了一个奇怪的问题。作者提供了一个实现“randoms”功能的例子:

randoms' :: (RandomGen g, Random a) => g -> [a]
randoms' gen = let (value, newGen) = random gen in value:randoms' newGen

好吧,这个编译得很好。但如果我将第二行更改为:

randoms' gen = (fst (random gen)) : (randoms' (snd (random gen)))

此文件报告加载错误:

IOlesson.hs:4:52:
    Ambiguous type variable `a' in the constraint:
      `Random a' arising from a use of `random' at IOlesson.hs:4:52-61
    Probable fix: add a type signature that fixes these type variable(s)
Failed, modules loaded: none.

如果我将此行更改为:

randoms' gen = (fst (random gen)) : (randoms' gen)

然后这样就可以了,正如预期的那样,这将返回所有相同元素的列表。

我很困惑:Miran的版本和我的版本有什么不同?

感谢您的任何想法!

3 个答案:

答案 0 :(得分:7)

问题是random接受RandGen的任何实例,并返回一个随机值和一个相同类型的新生成器。但随机值可以是任何类型,其实例为Random

random :: (Random a, RandomGen g) => g -> (a, g)

因此,当您在递归中第二次调用random时,它不知道第一个元素的类型应该是什么!没错,你真的不关心它(毕竟你用snd扔掉它),但 a 的选择会影响行为 random。所以为了消除歧义,你需要告诉GHC你希望 a 是什么。最简单的方法是按如下方式重写您的定义:

randoms' gen = let (value, gen') = random gen in value : randoms' gen'

因为您使用value作为结果列表的一部分,所以它必须与您的类型签名中的 a 具有相同的类型 - 结果列表的元素类型。解决了歧义,并且避免了下一个随机数的重复计算,以进行引导。有一些方法可以更直接地消除歧义(保留重复计算),但它们要么丑陋又令人困惑或涉及语言扩展。值得庆幸的是,你不应该经常遇到这种情况,当你这样做时,这样的方法应该可以解决歧义。

同样地,也许更整洁,你可以写:

randoms' gen = value : randoms' gen'
  where (value, gen') = random gen

答案 1 :(得分:4)

考虑random的类型:

random :: (RandomGen g, Random a) => g -> (a, g)

结果元组由任何类型的值Random和更新的RNG值组成。重要的部分是“任何实例”:不需要使用random来生成相同类型的值。

fst (random gen)中没有问题,因为生成的值是整个函数所需的任何类型;然而,在snd (random gen)中,随机值被丢弃,因此完全不知道它应该是什么类型。在不知道类型的情况下,Haskell无法选择要使用的相应Random实例,因此您看到的模糊类型错误。

答案 2 :(得分:1)

random的类型为:RandomGen g => g -> (a, g)

因此snd (random gen)仅为g -> g类型。然后它不知道a是什么。您可能希望生成的每种数据类型都有不同的random,但在这种情况下,编译器不知道您是否需要random :: g -> (Int,g)random :: g->(Char,g)或其他内容。

这解释了乳清(value, newGen) = random gen的作用。它有助于编译器将其与a的知识联系在一起。 value必须是a类型,因此可以推断出random gen的类型。

已编辑:我删除了我在修复时所做的错误尝试。只需坚持问题中的原始代码!)