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?
1 Answer 1
You can simplify the code by using span:
span :: (a -> Bool) -> [a] -> ([a], [a])
span
, applied to a predicatep
and a listxs
, returns a tuple where first element is longest prefix (possibly empty) ofxs
of elements that satisfyp
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
-
1\$\begingroup\$ 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. \$\endgroup\$Matthew– Matthew2015年01月25日 05:01:01 +00:00Commented Jan 25, 2015 at 5:01
-
2
-
\$\begingroup\$ Yeah, I've been using that lately, particularly to deal with
Maybe
s. The trick with this though is that I need to useget
andput
directly and then jump into regular list functions; that wasn't obvious to me until now. \$\endgroup\$Matthew– Matthew2015年01月25日 05:07:07 +00:00Commented Jan 25, 2015 at 5:07 -
2\$\begingroup\$ Use
state :: (s -> (a, s)) -> m a
to clean things up even further. I.E.,pop = state uncons
push = state (:)
andpopWhile p = state (span p)
. \$\endgroup\$bisserlis– bisserlis2015年01月25日 06:36:03 +00:00Commented Jan 25, 2015 at 6:36
Explore related questions
See similar questions with these tags.