I have been learning some Haskell little by little and am (slowly) working on understanding the State monad, attempting to write a function that repeats a State computation until the state meets some boolean test and collecting the returned values in a list for the overall result. I finally succeeded with this:
collectUntil :: (s -> Bool) -> State s a -> State s [a]
collectUntil f s = do s0 <- get
let (a,s') = runState s s0
put s'
if (f s') then return [a] else liftM (a:) $ collectUntil f s
so that
simpleState = state (\x -> (x,x+1))
*Main> evalState (collectUntil (>10) simpleState) 0
[0,1,2,3,4,5,6,7,8,9,10]
Is this a reasonable function for this task, or is there a more idiomatic way?
Each line where we call a function "in the state monad" behaves as follows: the arguments we give it on that line are applied, yielding a partially applied function. This is because each of the state monad functions yields a State function (which still needs an argument of type s before it can yields it's result tuple.
The Haskell type State describes functions that consume a state and produce both a result and an updated state, which are given back in a tuple. Here, s is the type of the state, and a the type of the produced result.
A monad is an algebraic structure in category theory, and in Haskell it is used to describe computations as sequences of steps, and to handle side effects such as state and IO. Monads are abstract, and they have many useful concrete instances. Monads provide a way to structure a program.
The I/O monad contains primitives which build composite actions, a process similar to joining statements in sequential order using `;' in other languages. Thus the monad serves as the glue which binds together the actions in a program.
You are making exactly the same mistakes that I made when I first started writing monadic code - making it way too complicated, overusing liftM
and underusing >>=
(equivalently, underusing the <-
notation).
Ideally, you shouldn't have to mention runState
or evalState
inside the state monad at all. The functionality you want is as follows:
f
, then returns
and add its result to the outputYou can do this quite directly as:
collectUntil f comp = do
s <- get -- Get the current state
if f s 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
Note that you can nest do statements if they are part of the same monad! This is very useful - it allows you to branch within one do block, as long as both branches of the if statement lead to something of the same monadic type.
The inferred type for this function is:
collectUntil :: MonadState t m => (t -> Bool) -> m a -> m [a]
If you wish, you can specialise that to the State s
type, although you don't have to:
collectUntil :: (s -> Bool) -> State s a -> State s [a]
It might even be preferable to keep the more general state, in case you want to use a different monad later.
Whenever s
is a stateful computation and you are inside the state monad, you can do
x <- s
and x
will now have the result of the computation (as if you'd called evalState
and fed in an initial state). If you ever need to check the state, you can do
s' <- get
and s'
will have the value of the current state.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With