Let's say I want to generate a list of items, while keeping track of some state. For example, generate [1..]
, while keeping track of the items generated so far, or generate a list of random numbers, keeping track of the RNG state.
It seems that there are two ways of doing this:
1) Generate a list of State Monads, [State s a]
, then run sequence
to get a State s [a]
. Then use runState
to get the [a]
.
2) Use Monad transformers somehow.
What's a good example of the monad transformers way? Which is more idiomatic? What's the pros and cons of each?
I've had to do quite a bit of this lately and I've found that [State s a]
and sequence
is the best option. I can't think of a useful/simple way of doing this with Monad Transformers. Even though list is a Monad, using sequence
means we don't have to worry about a Monad inside a Monad.
I've had to use MaybeT
for creating a Maybe Rand
, but never for lists and states. Though I've only been doing Haskell for a couple of months, so there are probably other people who can answer with more experience behind them.
However - it's not always about finding a way to use Monads. Sometimes I've found that it's easier not to use a Monad but instead use some of the higher order functions that come with Data.List.
Here are some ways of carrying forward a value with lists that doesn't involve the state Monad (as input into GHCi):
> :t scanl
scanl :: (a -> b -> a) -> a -> [b] -> [a]
> scanl (+) 0 [1,2,3,4,5]
[0,1,3,6,10,15]
> :t mapAccumL
mapAccumL :: (acc -> x -> (acc, y)) -> acc -> [x] -> (acc, [y])
> mapAccumL (\acc x -> (x+acc,x*2)) 0 [1,2,3,4,5]
(15,[2,4,6,8,10])
> :t unfoldr
unfoldr :: (b -> Maybe (a, b)) -> b -> [a]
> take 20 . unfoldr (\g -> Just $ randomR (0,10) g) $ mkStdGen 444
[2,3,8,0,7,5,2,10,10,5,10,2,4,2,8,9,1,1,5,10]
NB. you must import Data.List for scanl
, mapAccumL
and unfoldr
When working with Random numbers sometimes it's easier to generate a list of random numbers that we need rather than create a [Rand StdGen Int]
list. For example this function which generates a random sized list of random numbers using applicatives:
> :t runRand
runRand :: RandomGen g => Rand g a -> g -> (a, g)
> fst . flip runRand (mkStdGen 12345) $ take <$> getRandomR (10,15) <*> getRandomRs (10,20) :: [Int]
[17,16,16,19,16,17,15,12,10,11,11,10,17,12,13]