2

I'd like to have a lazily-generated list of random numbers, and I managed to do it but with unsafeInterleaveIO:

rs :: Random a => (a,a) -> IO [a]
rs b = do
  r <- randomRIO b
  ns <- unsafeInterleaveIO $ rs b
  return (r:ns)

Is there any safe way to accomplish this kind of values?

MasterMastic
  • 20,711
  • 12
  • 68
  • 90
  • 1
    This won't work in general (for the IO monad) because values may depend arbitrarily on effects of elements at any index, which for an infinite list includes elements at an infinite index. The function you want can't exist without unsafeInterleaveIO (which is actually pretty safe). – user2407038 Dec 29 '14 at 08:01

2 Answers2

5

If you want "lazily generated elements with effects", one solution is to eschew the conventional list type and use a List monad transformer, like ListT from the pipes library:

import System.Random
import Control.Monad
import Pipes
import qualified Pipes.Prelude as P

rs :: rs :: (Random a, MonadPlus m, MonadIO m) => (a,a) -> m a
rs b = liftIO (randomRIO b) `mplus` rs b

main :: IO ()
main = runEffect $ enumerate (rs (1::Int,10)) >-> P.take 5 >-> P.print

The result is:

*Main> :main
7
2
5
6
4

However, this bars you from using the conventional list functions to consume the "effectful list"; you are thrust into the pipes ecosystem.

(Applicative folds from the foldl package can also be used to consume the list, with the impurely and foldM auxiliary functions.)

The MonadPlus interface should be used as much as possible while defining effectful lists, as described here. It makes the effectful lists more library-agnostic.

danidiaz
  • 26,936
  • 4
  • 45
  • 95
3

A better way would probably be to generate a seed and then calculate the list using randoms:

randomRsIO :: Random a => (a, a) -> IO [a]
randomRsIO b = do
    g <- newStdGen
    return $ randomRs b g

Or simply

randomRsIO b = fmap (randomRs b) newStdGen
bheklilr
  • 53,530
  • 6
  • 107
  • 163
  • That's very specific to the example code. I'm looking for a general pattern to lazily construct lazy IO lists (not necessarily random lists). – MasterMastic Dec 29 '14 at 05:29
  • 1
    `mapM`, `forM`, `sequence`, `foldM`, and other monad combinators should work. If you can write an expression that generates 1 element, then you can write a function that generates any number of elements lazily using these functions. (At least for most cases). – bheklilr Dec 29 '14 at 05:33
  • An example would help. `sequence` was my first try and it didn't work. – MasterMastic Dec 29 '14 at 05:46
  • `sequence $ repeat $ randomRIO b` should do it. Not at a computer so I can't check, but that should yield a lazy infinite stream of random numbers. – bheklilr Dec 29 '14 at 05:55
  • Yeah that was pretty much my naive approach. It didn't work since when `sequence` binds the tail to the returned value it executes the IO (which is why I figured to `unsafeInterleaveIO`). Nevertheless thanks a lot. – MasterMastic Dec 29 '14 at 06:03
  • @bheklilr: that won't work, because `>>=` is strict for IO. To lazily construct a lazy list in IO, `unsafeInterleaveIO` is essentially the only way. In general, programmers prefer to seek other solutions (e.g. using ST instead of IO, returning a new IO action, etc), but sometimes there's nothing wrong with `unsafeInterleaveIO`. – John L Dec 29 '14 at 07:59