I have primitive interpreter written in haskell.
This interpreter can correctly handle return
statements (see my previous question).
Now I want to add global state to my interpreter.
This state could be changed from global code or from function's code
(function's code is runned with runCont
to provide return
logic).
The code is presented here:
import Control.Monad.Cont
import Control.Monad.State
type MyState = String
data Statement = Return Int | GetState | SetState MyState | FuncCall [Statement] deriving (Show)
data Value = Undefined | Value Int | StateValue MyState deriving (Show)
type Eval a = StateT MyState (Cont (Value, MyState)) a
runEval ::(Eval Value) -> MyState -> (Value, MyState)
runEval eval state = runCont (runStateT eval state) id
evalProg :: [Statement] -> Value
evalProg stmts = fst $ runEval (evalBlock stmts) $ ""
evalBlock :: [Statement] -> Eval Value
evalBlock [] = return Undefined
evalBlock [stmt] = evalStatment stmt
evalBlock (st:stmts) = evalStatment st >> evalBlock stmts
evalStatment :: Statement -> Eval Value
evalStatment (Return val) = do
state <- get
lift $ cont $ \_ -> (Value val, state)
evalStatment (SetState state) = put state >> return Undefined
evalStatment (FuncCall stmts) = do
-- I don't like this peace of code
state <- get
(value, newState) <- return $ runEval (evalBlock stmts) $ state
put newState
return value
evalStatment GetState = get >>= return . StateValue
test2 = evalProg [SetState "Hello", FuncCall [SetState "Galaxy", Return 3], GetState] -- result is StateValue "Galaxy"
This code works fine, but I don't like the evalStatment (FuncCall stmts)
part of this code.
I pass current state of interpreter to runEval
function,
then get back modified state and set it as new interpreter's state.
Is it possible to improve this code? Can I somehow make function's code (FuncCall
)
operate on interpreter's state implicitly (without getting current state and running function's
code and setting interpreter's new state explicitly) ?
I suggest you change your basic Monad to
type Eval a = ContT Value (State MyState) a
That way, the State MyState
part is at the bottom of the "monad transformer stack", and you will be able to more easily pull off only the upper continuation part without affecting the state. Then the FuncCall
case can be simply
evalStatment (FuncCall stmts) = lift $ runContT (evalBlock stmts) return
Of course this will require rewriting some other parts as well. But not much, and most of it actually gets simpler! Here are all the parts I needed to change:
type Eval a = ContT Value (State MyState) a
runEval eval state = runState (runContT eval return) state
evalStatment (Return val) = ContT $ \_ -> return (Value val)
evalStatment (FuncCall stmts) = lift $ runContT (evalBlock stmts) return
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