2014-05-17 28 views
5

Ho un interprete primitivo scritto in haskell. Questo interprete può gestire correttamente le dichiarazioni return (see my previous question).Il modo giusto per comporre i continuatori di stato e di continuazione

Ora voglio aggiungere lo stato globale al mio interprete. Questo stato può essere modificato dal codice globale o dal codice della funzione (il codice della funzione viene eseguito con runCont per fornire la logica return).

Il codice è presentato qui:

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" 

Questo codice funziona bene, ma non mi piace la parte evalStatment (FuncCall stmts) di questo codice. Passaggio allo stato attuale dell'interprete per la funzione runEval, quindi ripristinare lo stato modificato e impostarlo come nuovo stato dell'interprete.

È possibile migliorare questo codice? Posso in qualche modo rendere il codice della funzione (FuncCall) funzionare in modo implicito sullo stato dell'interprete (senza ottenere lo stato corrente e il codice della funzione di esecuzione e impostare esplicitamente il nuovo stato dell'interprete)?

+0

Hai solo bisogno di 'sequenza :: Monad m => [m a] -> m [a]'? Possibilmente seguito da 'liftM last :: Monad m => m [a] -> m a'? –

risposta

4

ti suggerisco di cambiare la vostra Monade di base per

type Eval a = ContT Value (State MyState) a 

In questo modo, la parte State MyState è in fondo della "monade, trasformatore di stack", e sarete in grado di tirare più facilmente fuori solo la parte superiore parte di continuazione senza intaccare lo stato. Allora il caso FuncCall può essere semplicemente

evalStatment (FuncCall stmts) = lift $ runContT (evalBlock stmts) return 

Naturalmente ciò richiederà riscrittura alcune altre parti pure. Ma non molto, e la maggior parte in realtà diventa più semplice! Qui ci sono tutte le parti che dovevo cambiare:

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 
+0

grazie, questo è quello che stavo cercando! – sergeyz

Problemi correlati