7

我正在阅读 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)

然后这会很好,并且正如预期的那样,这将返回所有相同元素的列表。

我不解:米兰的版本和我的版本有什么不同?

感谢您的任何想法!

4

3 回答 3

7

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

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

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

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

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

等效地,也许更简洁,你可以写:

randoms' gen = value : randoms' gen'
  where (value, gen') = random gen
于 2011-12-26T20:36:37.290 回答
4

考虑以下类型random

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

结果元组包含任何类型的值,它是 的实例Random,以及更新的 RNG 值。重要的部分是“任何实例”:没有什么需要两个使用random来产生相同类型的值。

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

于 2011-12-26T20:37:41.270 回答
1

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

因此snd (random gen)只有 type g -> g。然后它不知道是什么arandom您可能想要生成的每种数据类型都有不同的类型,但在这种情况下,编译器不知道您是否想要 arandom :: g -> (Int,g)或 arandom :: g->(Char,g)或其他东西。

这解释了它们(value, newGen) = random gen的工作原理。它可以帮助编译器将它的知识联系在一起avalue必须是类型a,因此它可以推断出 的类型random gen

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

于 2011-12-26T20:39:29.967 回答