Is mapM in Haskell strict? Why does this program get a stack overflow?

前端 未结 2 2082
忘掉有多难
忘掉有多难 2020-12-20 16:48

The following program terminates correctly:

import System.Random

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

main = do
  r         


        
相关标签:
2条回答
  • 2020-12-20 17:33

    Random numbers in general are not strict, but monadic binding is--the problem here is that mapM has to sequence the entire list. Consider its type signature, (a -> m b) -> [a] -> m [b]; as this implies, what it does is first map the list of type [a] into a list of type [m b], then sequence that list to get a result of type m [b]. So, when you bind the result of applying mapM, e.g. by putting it on the right-hand side of <-, what this means is "map this function over the list, then execute each monadic action, and combine the results back into a single list". If the list is infinite, this of course won't terminate.

    If you simply want a stream of random numbers, you need to generate the list without using a monad for each number. I'm not entirely sure why you've used the design you have, but the basic idea is this: Given a seed value, use a pseudo-random number generator to produce a pair of 1) a random number 2) a new seed, then repeat with the new seed. Any given seed will of course provide the same sequence each time. So, you can use the function getStdGen, which will provide a fresh seed in the IO monad; you can then use that seed to create an infinite sequence in completely pure code.

    In fact, System.Random provides functions for precisely that purpose, randoms or randomRs instead of random and randomR.

    If for some reason you want to do it yourself, what you want is essentially an unfold. The function unfoldr from Data.List has the type signature (b -> Maybe (a, b)) -> b -> [a], which is fairly self-explanatory: Given a value of type b, it applies the function to get either something of type a and a new generator value of type b, or Nothing to indicate the end of the sequence.

    You want an infinite list, so will never need to return Nothing. Thus, partially applying randomR to the desired range and composing it with Just gives this:

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

    Feeding that into unfoldr gives this:

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

    ...which does exactly as it claims: Given an instance of RandomGen, it will produce an infinite (and lazy) list of random numbers generated from that seed.

    0 讨论(0)
  • I would do something more like this, letting randomRs do the work with an initial 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
    

    As for the laziness, what's happening here is that mapM is (sequence . map)

    Its type is: mapM :: (Monad m) => (a -> m b) -> [a] -> m [b]

    It's mapping the function, giving a [m b] and then needs to execute all those actions to make an m [b]. It's the sequence that'll never get through the infinite list.

    This is explained better in the answers to a prior question: Is Haskell's mapM not lazy?

    0 讨论(0)
提交回复
热议问题