You make the same mistakes as I did when I first started writing monadic code, making it too complicated using liftM
and using >>=
(equivalently using the <-
notation).
Ideally, you do not need to mention runState
or evalState
inside the state monad in general. The required functionality is as follows:
- Read current status
- If it satisfies the predicate
f
, return - If not, then run the calculation of
s
and add its result to the output
You can do it right this way:
collectUntil f comp = do s <- get -- Get the current state if fs then return [] -- If it satisfies predicate, return else do -- Otherwise... x <- comp -- Perform the computation s xs <- collectUntil f comp -- Perform the rest of the computation return (x:xs) -- Collect the results and return them
Please note that you can insert statements if they are part of the same monad! This is very useful - it allows you to embed in one do block if both branches of the if statement lead to something of the same monadic type.
The deduced type for this function:
collectUntil :: MonadState tm => (t -> Bool) -> ma -> m [a]
If you want, you can specialize in type State s
, although you do not need to:
collectUntil :: (s -> Bool) -> State sa -> State s [a]
It might even be preferable to maintain a more general state if you want to use another monad later.
What is intuition?
Whenever s
is a stateful calculation and you are inside the state monad, you can do
x <- s
and x
will now have the result of the calculation (as if you had called evalState
and sent it to its original state). If you need to check the status, you can do
s' <- get
and s'
will be the value of the current state.
source share