Prev: State モナド TOC: 目次 Next: Writer モナド

Reader モナド


概観

計算のタイプ: 共有された環境から値を読み込む計算
束縛の戦略: モナドの値は環境から値への関数。束縛された関数は束縛された値に 適用され、両方とも共有された環境にアクセスする
利用場面: 変数束縛あるいは他の共有環境の保持
ゼロおよびプラス: なし
型の例: Reader [(String,Value)] a

動機

いくつかのプログラミングの課題では、(変数束縛の集合のような)共有された 環境内での計算が必要となる。こうした計算では、典型的にはその環境から 値を読み、ときには変更された(たとえば、新しい束縛や、覆い隠す束縛など) 環境のなかで部分的な計算を実行しますが、完全に一般化された State モナド までは必要ありません。

Reader モナドはこのようなタイプの計算のために特化して設計されています。 そして、多くの場合 State モナドを使うよりも明瞭で簡単な機構です。

定義

ここで示した定義では Haskell 98 の標準にはない、複数パラメータ型クラスお よび funDeps が使われています。この Reader モナドを利用するのに、これの 詳細を完全には理解する必要はありません。

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

Reader モナド中の値はある環境からある値への関数です。Reader モナドの 計算から最終的な値を取り出すには (runReader reader) を環境 の値に適用するだけですみます。

return 関数は環境を無視し与えられた値を生成する Reader を作成します。束縛演算子は、環境を使い、左側の引数 から値を取り出し、同じ環境で、その値に対して束縛された関数を適用する Reader を生成します。

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

MonadReader クラスは Reader モナドを使うときに大変便利な 関数をいくつも提供します。ask 関数は環境を検索します。 local 関数は変更された環境のなかで計算を実行します。 asks 関数は現在の環境を引数とする関数の返り値を得る便利関数で、 選択子関数あるいは探索関数とともに使うのが一般的です。

変数置換やテンプレートのインクルードを含むテンプレートを実体化する問題を 考えましょう。Reader モナドを使えば、既知のテンプレートあるいは既知の 変数束縛の環境を保持することができます。そうすれば、変数置換が起こった ときに、asks 関数を使って変数の値を探索できます。 テンプレートが新しい変数の定義を伴って,インクルードされた場合、 local 関数を使って、追加された変数束縛を含む変更のあった 環境で、テンプレートの値を解決することができます。

example16.hs で利用できるコード
-- これはテンプレートの抽象構文表現です
--              Text       Variable     Quote        Include                   Compound
data Template = T String | V Template | Q Template | I Template [Definition] | C [Template]
data Definition = D Template Template

-- 環境は名前付きテンプレートの連想リストと名前付きの変数値の
-- 連想リストで構成されています
data Environment = Env {templates::[(String,Template)],
                        variables::[(String,String)]}

-- 環境から変数を探索
lookupVar :: String -> Environment -> Maybe String
lookupVar name env = lookup name (variables env)

-- 環境からテンプレートを探索
lookupTemplate :: String -> Environment -> Maybe Template
lookupTemplate name env = lookup name (templates env)

-- 環境に解決された定義のリストを追加
addDefs :: [(String,String)] -> Environment -> Environment
addDefs defs env = env {variables = defs ++ (variables env)}
                      
-- Definition を解決し、(name,value) の組を生成
resolveDef :: Definition -> Reader Environment (String,String)
resolveDef (D t d) = do name <- resolve t
                        value <- resolve d
                        return (name,value)

-- テンプレートを解決し文字列にする
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)

テンプレート t を解決し、String にするのに 必要なのは単に runReader (resolve t) env を実行することです。


Prev: State モナド TOC: 目次 Next: Writer モナド