Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Working with the State monad in Haskell

Tags:

haskell

monads

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?

like image 240
Dan T Avatar asked Jun 28 '12 17:06

Dan T


People also ask

How does the state monad work?

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.

How does state work in Haskell?

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.

How do monads work Haskell?

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.

How does the IO monad work?

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.


1 Answers

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:

  • Read the current state
  • If it satisfies the predicate f, then return
  • If not, then run the computation s and add its result to the output

You 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.

What's the intuition?

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.

like image 50
Chris Taylor Avatar answered Sep 22 '22 04:09

Chris Taylor