Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Right way to compose continuation and state monad transformers

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) ?

like image 776
sergeyz Avatar asked May 17 '14 20:05

sergeyz


1 Answers

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
like image 142
Ørjan Johansen Avatar answered Oct 03 '22 05:10

Ørjan Johansen