Haskell中的mapM是否严格?为什么这个程序会出现堆栈溢出?

时间:2010-07-29 01:50:48

标签: haskell monads lazy-evaluation

以下程序正确终止:

import System.Random

randomList = mapM (\_->getStdRandom (randomR (0, 50000::Int))) [0..5000]

main = do
  randomInts <- randomList
  print $ take 5 randomInts

运行:

$ runhaskell test.hs
[26156,7258,29057,40002,26339]

但是,用无限列表提供它,程序永远不会终止,并且在编译时,最终会产生堆栈溢出错误!

import System.Random

randomList = mapM (\_->getStdRandom (randomR (0, 50000::Int))) [0..]

main = do
  randomInts <- randomList
  print $ take 5 randomInts

运行,

$ ./test
Stack space overflow: current size 8388608 bytes.
Use `+RTS -Ksize -RTS' to increase it.

我希望每次从列表中选择一个项目时,程序会懒惰地评估getStdRandom,这样做5次之后就完成了。为什么要试图评估整个清单?

感谢。

有没有更好的方法来获得无限的随机数列表?我想将此列表传递给纯函数。

编辑:更多阅读揭示了该功能

randomList r = do g <- getStdGen
                  return $ randomRs r g

是我正在寻找的。

EDIT2:在看完camccann的回答之后,我意识到getStdGen每次通话都会获得新的种子。相反,最好将此功能用作简单的一次性随机列表生成器:

import System.Random

randomList :: Random a => a -> a -> IO [a]
randomList r g = do s <- newStdGen
                    return $ randomRs (r,g) s

main = do r <- randomList 0 (50::Int)
          print $ take 5 r

但我仍然不明白为什么我的mapM电话没有终止。显然与随机数无关,但可能与mapM有关。

例如,我发现以下内容也没有终止:

randomList = mapM (\_->return 0) [0..]

main = do
  randomInts <- randomList
  print $ take 50000 randomInts

是什么给出的?顺便说一句,恕我直言,上述randomInts函数应该在System.Random。能够简单在IO monad中生成随机列表并在需要时将其传递给纯函数非常方便,我不明白为什么这不应该在标准库中。< / p>

2 个答案:

答案 0 :(得分:12)

一般的随机数并不严格,但 monadic binding - 这里的问题是mapM必须对整个列表进行排序。考虑其类型签名(a -> m b) -> [a] -> m [b];这意味着,它所做的是先将map类型[a]的列表放入类型[m b]的列表中,然后列出sequence以获得类型{{m [b]的结果1}}。因此,当您将mapM例如的结果绑定到<-的右侧时,这意味着“将此函数映射到列表中,然后执行每个monadic动作,并将结果合并回一个列表“。如果列表是无限的,这当然不会终止。

如果您只是想要一个随机数流,则需要生成列表而不使用每个数字的monad。我不完全确定你为什么使用你的设计,但基本的想法是:给定种子值,使用伪随机数生成器生成一对1)随机数2)新种子,然后重复新种子。任何给定的种子当然每次都提供相同的序列。因此,您可以使用函数getStdGen,它将在IO monad中提供新种子;然后,您可以使用该种子在完全纯粹的代码中创建无限序列。

事实上,System.Random提供的功能正是为了这个目的,randomsrandomRs而不是randomrandomR

如果由于某种原因你想自己做,你想要的基本上是展开。来自unfoldr的函数Data.List具有类型签名(b -> Maybe (a, b)) -> b -> [a],这是相当不言自明的:给定类型b的值,它应用函数来获取键入a以及类型为b的新生成器值,或Nothing以指示序列的结束。

您想要一个无限列表,因此永远不需要返回Nothing。因此,将randomR部分应用到所需范围并使用Just进行组合会产生以下结果:

Just . randomR (0, 50000::Int) :: (RandomGen a) => a -> Maybe (Int, a)

将其提供到unfoldr可以得到:

unfoldr (Just . randomR (0, 50000::Int)) :: (RandomGen a) => a -> [Int]

...完全按照它声明的那样:给定一个RandomGen的实例,它将产生一个无限(和 lazy )从该种子生成的随机数列表。

答案 1 :(得分:5)

我会做更像这样的事情,让randomRs用初始的RandomGen来完成工作:

#! /usr/bin/env runhaskell

import Control.Monad
import System.Random


randomList :: RandomGen g => g -> [Int]
randomList = randomRs (0, 50000)

main :: IO ()
main = do
   randomInts <- liftM randomList newStdGen
   print $ take 5 randomInts

至于懒惰,这里发生的事情是mapM(sequence . map)

其类型为:mapM :: (Monad m) => (a -> m b) -> [a] -> m [b]

它正在映射函数,给出[m b],然后需要执行所有这些操作来生成m [b]。这是永远无法通过无限列表的序列。

在先前问题的答案中更好地解释了这一点:Is Haskell's mapM not lazy?