Prev: The State monad TOC: Contents Next: The Writer monad

The Reader monad


Overview

Computation type: Computations which read values from a shared environment.
Binding strategy: Monad values are functions from the environment to a value. The bound function is applied to the bound value, and both have access to the shared environment.
Useful for: Maintaining variable bindings, or other shared environment.
Zero and plus: None.
Example type: Reader [(String,Value)] a

Motivation

Some programming problems require computations within a shared environment (such as a set of variable bindings). These computations typically read values from the environment and sometimes execute sub-computations in a modified environment (with new or shadowing bindings, for example), but they do not require the full generality of the State monad.

The Reader monad is specifically designed for these types of computations and is often a clearer and easier mechanism than using the State monad.

Definition

The definition shown here uses multi-parameter type classes and funDeps, which are not standard Haskell 98. It is not necessary to fully understand these details to make use of the Reader monad.

newtype Reader e a = Reader { runReader :: (e -> a) }
 
instance Monad (Reader e) where 
    return a         = Reader $ \e -> a 
    (Reader r) >>= f = Reader $ \e -> f (r e) e 

Values in the Reader monad are functions from an environment to a value. To extract the final value from a computation in the Reader monad, you simply apply (runReader reader) to an environment value.

The return function creates a Reader that ignores the environment and produces the given value. The binding operator produces a Reader that uses the environment to extract the value its left-hand side and then applies the bound function to that value in the same environment.

class MonadReader e m | m -> e where 
    ask   :: m e
    local :: (e -> e) -> m a -> m a 
 
instance MonadReader (Reader e) where 
    ask       = Reader id 
    local f c = Reader $ \e -> runReader c (f e) 
 
asks :: (MonadReader e m) => (e -> a) -> m a 
asks sel = ask >>= return . sel

The MonadReader class provides a number of convenience functions that are very useful when working with a Reader monad. The ask function retrieves the environment and the local function executes a computation in a modified environment. The asks function is a convenience function that retrieves a function of the current environment, and is typically used with a selector or lookup function.

Example

Consider the problem of instantiating templates which contain variable substitutions and included templates. Using the Reader monad, we can maintain an environment of all known templates and all known variable bindings. Then, when a variable substitution is encountered, we can use the asks function to lookup the value of the variable. When a template is included with new variable definitions, we can use the local function to resolve the template in a modified environment that contains the additional variable bindings.

Code available in example16.hs
-- This the abstract syntax representation of a template
--              Text       Variable     Quote        Include                   Compound
data Template = T String | V Template | Q Template | I Template [Definition] | C [Template]
data Definition = D Template Template

-- Our environment consists of an association list of named templates and
-- an association list of named variable values. 
data Environment = Env {templates::[(String,Template)],
                        variables::[(String,String)]}

-- lookup a variable from the environment
lookupVar :: String -> Environment -> Maybe String
lookupVar name env = lookup name (variables env)

-- lookup a template from the environment
lookupTemplate :: String -> Environment -> Maybe Template
lookupTemplate name env = lookup name (templates env)

-- add a list of resolved definitions to the environment
addDefs :: [(String,String)] -> Environment -> Environment
addDefs defs env = env {variables = defs ++ (variables env)}
                      
-- resolve a Definition and produce a (name,value) pair
resolveDef :: Definition -> Reader Environment (String,String)
resolveDef (D t d) = do name <- resolve t
                        value <- resolve d
                        return (name,value)

-- resolve a template into a string
resolve :: Template -> Reader Environment (String)
resolve (T s)    = return s
resolve (V t)    = do varName  <- resolve t
                      varValue <- asks (lookupVar varName)
		      return $ maybe "" id varValue
resolve (Q t)    = do tmplName <- resolve t
                      body     <- asks (lookupTemplate tmplName)
                      return $ maybe "" show body 
resolve (I t ds) = do tmplName <- resolve t
                      body     <- asks (lookupTemplate tmplName)
                      case body of
                        Just t' -> do defs <- mapM resolveDef ds
                                      local (addDefs defs) (resolve t')
                        Nothing -> return ""
resolve (C ts)   = (liftM concat) (mapM resolve ts)

To use the Reader monad to resolve a template t into a String, you simply need to do runReader (resolve t) env.


Prev: The State monad TOC: Contents Next: The Writer monad