# Popping from a list in state while a condition is true

I'm dealing with data that stores its state as a String, treating the string like a stack. I also have to combine that with error handling.

To that end, I'm using the type StateT String Maybe a. I have a function to pop and to push a Char from and to the string:

pop :: StateT String Maybe Char
pop = do
x:xs <- get
put xs
return x

push :: Char -> StateT String Maybe ()
push x = do
xs <- get
put (x:xs)
return ()


I wrote a function to repeatedly pop from the string while the characters being popped fulfilled a condition. It behaves as follows:

> runStateT (popWhile (<'a')) "HELLO world"
Just ("HELLO ","world")

> runStateT (popWhile (>'a')) "HELLO world"
Just ("","HELLO world")


My implementation is the following:

popWhile :: (Char -> Bool) -> StateT String Maybe [Char]
popWhile f = do
s <- get
if null s
then return []
else popAgain

where
popAgain = do
x <- pop
if f x
then liftM (x:) (popWhile f)
else push x >> return []


But that seems pretty bulky, and has two if then else's in it. Is there a better way to write this function?

You can simplify the code by using span:

span :: (a -> Bool) -> [a] -> ([a], [a])

span, applied to a predicate p and a list xs, returns a tuple where first element is longest prefix (possibly empty) of xs of elements that satisfy p and second element is the remainder of the list

popWhile :: (Char -> Bool) -> StateT String Maybe String
popWhile p = do
s <- get
let (xs, ys) = span p s
put ys
return xs


Thanks to @bisserlis for the suggestion to use state

popWhile = state . span

• Ah, this is exactly what I was looking for, thanks! I'm still pretty beginner when it comes to monads in Haskell, so I've a lot to learn about writing functions like this. In that sense, this is a super helpful answer, so I'll be giving this the Green Tick of Doom. – Matthew Jan 25 '15 at 5:01
• @Matthew glad I could help :) Hoogle is a very useful resource for finding functions like span. – mjolka Jan 25 '15 at 5:05
• Yeah, I've been using that lately, particularly to deal with Maybes. The trick with this though is that I need to use get and put directly and then jump into regular list functions; that wasn't obvious to me until now. – Matthew Jan 25 '15 at 5:07
• Use state :: (s -> (a, s)) -> m a to clean things up even further. I.E., pop = state uncons push = state (:) and popWhile p = state (span p). – bisserlis Jan 25 '15 at 6:36
• @bisserlis that's a great suggestion, thank you! – mjolka Jan 25 '15 at 6:39