Getting State environment in a monadic context in an interpreter

Viewed 57

I wanna get used to working monad transformers so I thought I'd write a simple interpreter using ExceptT and State, where my state is Map Ident Int, Ident being an alias for Strings.

My syntax definition looks like

data BinOp
  = Plus
  | Minus
  | Times
  | Div
  deriving (Eq, Show)

data Stmt 
  = CompoundStmt Stmt Stmt
  | AssignStmt Ident Exp
  | PrintStmt [Exp]
  deriving (Eq, Show)

data Exp 
  = IdentExp Ident
  | NumExp Int
  | OpExp Exp BinOp Exp
  | SeqExp Stmt Exp
  deriving (Eq, Show)

but that's not that relevant. I got to evaluating identifier expressions when I realized I have no idea how to actually fetch my environment:

interpIdentExp :: (MonadState Env m, MonadError EvalError m) => Ident -> m Int
interpIdentExp ident = do
  env <- get
  fromMaybe (throwError IdNotDefined) (M.lookup ident env)

was my first go at it, but env is actually Map Ident (m Int), whereas I just wanna deal with plain Ints and I'm not exactly sure how to make it work.

1 Answers

I assume the last bit is a misprint, and Env is defined as Map Ident Int rather than Map Ident (m Int).

The following should work:

interpIdentExp :: (MonadState Env m, MonadError EvalError m) => Ident -> m Int
interpIdentExp ident = do
  env <- get
  maybe (throwError IdNotDefined) return (M.lookup ident env)

though some might prefer to make it more obvious how the state is being accessed by writing:

interpIdentExp :: (MonadState Env m, MonadError EvalError m) => Ident -> m Int
interpIdentExp ident = do
  mval <- gets (M.lookup ident)
  maybe (throwError IdNotDefined) return mval

Personally, I think the more verbose case statement makes the error handling easier to read, so I'd favor:

interpIdentExp :: (MonadState Env m, MonadError EvalError m) => Ident -> m Int
interpIdentExp ident = do
  mval <- gets (M.lookup ident)
  case mval of
    Just val -> return val
    Nothing -> throwError IdNotDefined
Related