I'm trying to calculate growing list with random number. The basic code is here:
import System.Random
randint :: IO Int
randint = randomRIO (0,1)
type Generation = [Int]
gnext :: Generation -> IO Generation
gnext g = flip (:) g <$> randint
gnext
can be more complex.
Now following code behaved as expected:
test1 = do
let b0 = []
b1 <- gnext b0
b2 <- gnext b1
b3 <- gnext b2
b4 <- gnext b3
return [b0, b1, b2, b3, b4]
ghci> test1
[[],[0],[1,0],[1,1,0],[1,1,1,0]]
We can see the n-th list has the (n-1)-th list in its cdr part.
On the other hand, following code failed (because of lazy evaluation, I think).
test2 = sequence $ take 5 $ iterate (gnext =<<) (return [])
ghci> test2
[[],[0],[0,0],[1,0,1],[1,1,1,0]]
This result is completely random.
How can I rewrite test1
code by test2
style?
Thank you.
CodePudding user response:
What's the problem with sequence $ take 5 $ iterate (gnext =<<) (return [])
?
Let's reduce iterate (gnext =<<) (return [])
:
iterate (gnext =<<) (return []) =>
[ return []
, gnext =<< return []
, gnext =<< gnext =<< return []
, gnext =<< gnext =<< gnext =<< return []
, gnext =<< gnext =<< gnext =<< gnext =<< return []
, ...
]
Next reduce take 5 $ iterate (gnext =<<) (return [])
:
take 5 $ iterate (gnext =<<) (return []) =>
[ return []
, gnext =<< return []
, gnext =<< gnext =<< return []
, gnext =<< gnext =<< gnext =<< return []
, gnext =<< gnext =<< gnext =<< gnext =<< return []
]
And finally, reduce sequence $ take 5 $ iterate (gnext =<<) (return [])
:
sequence $ take 5 $ iterate (gnext =<<) (return []) =>
sequence [ return []
, gnext =<< return []
, gnext =<< gnext =<< return []
, gnext =<< gnext =<< gnext =<< return []
, gnext =<< gnext =<< gnext =<< gnext =<< return []
]
which equivalent to:
do
b0 <- return []
b1 <- gnext =<< return []
b2 <- gnext =<< gnext =<< return []
b3 <- gnext =<< gnext =<< gnext =<< return []
b4 <- gnext =<< gnext =<< gnext =<< gnext =<< return []
return [b0, b1, b2, b3, b4]
As you can see, this is not what you want, and lazy evaluation has nothing to do with it.
You can solve this problem with the state monad:
gnextM :: StateT Generation IO Generation
gnextM = do
g0 <- get
g1 <- liftIO $ gnext g0
put g1
return g1
test2 = flip evalStateT [] $ sequence $ take 5 $ repeat gnextM
or:
gnextM :: StateT [Generation] IO ()
gnextM = do
b0 <- gets head
b1 <- liftIO $ gnext b0
modify (b1:)
runGen :: StateT [Generation] IO a -> Generation -> IO [Generation]
runGen gnext s = execStateT gnext [s]
test2 = flip runGen [] $ replicateM 5 gnextM
depending on what you want.
CodePudding user response:
following code failed (because of lazy evaluation, I think).
It’s not really due to lazy evaluation. In test1
, you share the chain of results of all the actions, while in test2
, you only share the actions themselves before sequencing them together, and their results are unrelated:
test2 = sequence (take 5 actions)
where
actions
= (return [])
: [(gnext =<< a) | a <- actions]
test2 = sequence
[ return []
, gnext =<< return []
, gnext =<< gnext =<< return []
, gnext =<< gnext =<< gnext =<< return []
, gnext =<< gnext =<< gnext =<< gnext =<< return []
]
So the issue is that sequence (take 5 actions)
doesn’t express the relationship you want, which is something more like fmap (take 5) (sequence actions)
. Unfortunately, that doesn’t produce a result at all, because sequence
in IO
is too strict: it tries to consume the infinite stream of actions
before producing a result at all.
Instead, it’s typical to use patterns such as tails <$> replicateM 5 randint
when you know the length ahead of time, or combinators like unfoldrM
if not:
test = unfoldrM step (b0, 5)
where
b0 = []
step (b, i)
| i == 0 = pure Nothing
| otherwise = do
b' <- gnext b
pure (Just (b', (b', i - 1)))
-- Available in ‘monad-loops’ or as below.
unfoldrM
:: (Monad m)
=> (a -> m (Maybe (b, a))) -> a -> m [b]
unfoldrM f = go
where
go a = do
m <- f a
case m of
Just (b, a') -> do
bs <- go a'
pure (b : bs)
Nothing -> pure []