TheIronKnuckle
TheIronKnuckle

Reputation: 7294

Making normal monadic functions work with the monad transformer equivalent

I'm trying to solve the balanced brackets problem. I don't want to do continuous IO, and would rather make a single call to getLine and parse the resulting string. Therefore the function that solves the problem will deal with two different states: The unconsumed part of the input string, and the bracket stack.

I want to set up some functions for manipulating a stack:

type Stack = String

pop :: Stack -> (Char,Stack)
pop (x:xs) = (x,xs)

push :: Char -> Stack -> ((),Stack)
push a xs = ((),a:xs)

That's all good if I'm operating in the state monad, however I'm operating in the StateT monad

balanced :: StateT Stack (State String) Bool

I know I've been told not to have duplicate monads in the stack. I'm doing it this way because I like how it simplifies the push and pop definitions.

Two problems:

  1. No matter what I do I can't find a way to apply push and pop to the Stack contained in the StateT.
  2. I have no idea how to call this from the main function

Here's the rest of the code

next :: String -> (Maybe Char,String)
next ""     = (Nothing,[])
next (x:xs) = (Just x,xs)

balanced = do
            c <- lift (state next)
            case c of
              Nothing -> return True
              Just c  -> if elem c open 
                         then (push c) >> balanced
                         else if elem c close 
                              then pop >>= \x ->
                                if eq x c
                                then balanced
                                else return False
                              else balanced
          where open  = "<{(["
                close = "])}>"
                eq '(' ')' = True
                eq '{' '}' = True
                eq '<' '>' = True
                eq '[' ']' = True
                eq  _   _  = False

Upvotes: 5

Views: 226

Answers (1)

shang
shang

Reputation: 24802

Your problem is that your push and pop are just ordinary, non-monadic function which you are trying to use in the monadic do-block. You are using next correctly, since you call it using the state function, but as you probably noticed, state only works for the plain State monad and not StateT.

We can implement a monad transformer version of state like this:

stateT :: Monad m => (s -> (a, s)) -> StateT s m a
stateT f = do
    (x, s') <- gets f
    put s'
    return x

And then use it in the balanced function with push and pop.

balanced :: StateT Stack (State String) Bool
balanced = do
            c <- lift (state next)
            case c of
              Nothing -> return True
              Just c  -> if elem c open
                         then (stateT $ push c) >> balanced
                         else if elem c close
                              then stateT pop >>= \x ->
                                if eq x c
                                    then balanced
                                    else return False
                              else balanced
          where open  = "<{(["
                close = "])}>"
                eq '(' ')' = True
                eq '{' '}' = True
                eq '<' '>' = True
                eq '[' ']' = True
                eq  _   _  = False

The function is called like this:

evalState (evalStateT balanced []) s

Where s is the initial string and [] is the initial stack.

Upvotes: 7

Related Questions