[解決済み] リーダーモナドの目的は何ですか?
質問
リーダーモナドはとても複雑で、使い物にならないように思えます。JavaやC++のような命令型言語では、私の記憶違いでなければ、リーダーモナドに相当する概念は存在しません。
簡単な例を挙げて、これを少しクリアにしてもらえませんか?
どのように解決するのですか?
怖がらないでください。 リーダーモナドは実はそれほど複雑ではなく、本当に使いやすいユーティリティを持っているのです。
モナドにアプローチする方法は2つあります。
- モナドは何をするのか する ? どのような機能を備えていますか? どんなことに使えるの?
- モナドはどのように実装されるのですか? どこから発生するのでしょうか?
最初のアプローチから、リーダーモナドは何らかの抽象的な型である
data Reader env a
そのような
-- Reader is a monad
instance Monad (Reader env)
-- and we have a function to get its environment
ask :: Reader env env
-- finally, we can run a Reader
runReader :: Reader env a -> env -> a
では、これをどう使うか? リーダーモナドは(暗黙の)設定情報を計算で渡すのに適しています。
計算の中に様々な場面で必要となる定数があり、本当は同じ計算を異なる値で実行できるようにしたい場合は、いつでもリーダーモナドを使用する必要があります。
リーダーモナドは、OOの人たちが言うところの 依存性注入 . 例えば ネガマックス アルゴリズムは、2人用ゲームにおけるポジションの値を計算するために、(高度に最適化された形で)頻繁に使用されています。 しかし、アルゴリズム自体は、ゲームにおいて次のポジションが何であるかを決定できる必要があることと、現在のポジションが勝利ポジションであるかどうかを判断できる必要があることを除いて、どのようなゲームをプレイしているかを気にすることはありません。
import Control.Monad.Reader
data GameState = NotOver | FirstPlayerWin | SecondPlayerWin | Tie
data Game position
= Game {
getNext :: position -> [position],
getState :: position -> GameState
}
getNext' :: position -> Reader (Game position) [position]
getNext' position
= do game <- ask
return $ getNext game position
getState' :: position -> Reader (Game position) GameState
getState' position
= do game <- ask
return $ getState game position
negamax :: Double -> position -> Reader (Game position) Double
negamax color position
= do state <- getState' position
case state of
FirstPlayerWin -> return color
SecondPlayerWin -> return $ negate color
Tie -> return 0
NotOver -> do possible <- getNext' position
values <- mapM ((liftM negate) . negamax (negate color)) possible
return $ maximum values
これは、有限で決定論的な2人用のゲームであれば、動作します。
このパターンは本当に依存性注入でないものにも有用です。例えば、あなたが金融の仕事をしていて、資産(例えばデリバティブ)に値段をつけるための複雑なロジックを設計しているとします。しかし、多通貨を扱うためにプログラムを変更することになります。 この場合、通貨間の変換が必要です。最初の試みは、トップレベルの関数を定義することです。
type CurrencyDict = Map CurrencyName Dollars
currencyDict :: CurrencyDict
で、スポット価格を取得します。 そして、この辞書をコードで呼び出すことができます......が、待てよ! しかし、それはうまくいきません! 通貨辞書は不変なので、プログラムの寿命が尽きるまで同じである必要があります。 がコンパイルされたときから同じでなければなりません。 ! では、どうすればいいのでしょうか? まあ、1つの選択肢はReaderモナドを使うことでしょう。
computePrice :: Reader CurrencyDict Dollars
computePrice
= do currencyDict <- ask
--insert computation here
おそらく最も古典的な使用例は、インタプリタの実装でしょう。 しかし、それを見る前に、もう一つの機能である
local :: (env -> env) -> Reader env a -> Reader env a
なるほど、Haskellをはじめとする関数型言語は ラムダ計算 . ラムダ計算の文法は次のようなものです。
data Term = Apply Term Term | Lambda String Term | Var Term deriving (Show)
で、この言語のための評価器を書きたいと思います。そのためには、用語に関連するバインディングのリストである環境を追跡する必要があります(実際には、静的スコープを行いたいのでクロージャになるでしょう)。
newtype Env = Env ([(String, Closure)])
type Closure = (Term, Env)
終了したら、値(またはエラー)を出すようにします。
data Value = Lam String Closure | Failure String
では、インタプリタを書いてみましょう。
interp' :: Term -> Reader Env Value
--when we have a lambda term, we can just return it
interp' (Lambda nv t)
= do env <- ask
return $ Lam nv (t, env)
--when we run into a value, we look it up in the environment
interp' (Var v)
= do (Env env) <- ask
case lookup (show v) env of
-- if it is not in the environment we have a problem
Nothing -> return . Failure $ "unbound variable: " ++ (show v)
-- if it is in the environment, then we should interpret it
Just (term, env) -> local (const env) $ interp' term
--the complicated case is an application
interp' (Apply t1 t2)
= do v1 <- interp' t1
case v1 of
Failure s -> return (Failure s)
Lam nv clos -> local (\(Env ls) -> Env ((nv, clos) : ls)) $ interp' t2
--I guess not that complicated!
最後に、些細な環境を渡すことで使用できるようになります。
interp :: Term -> Value
interp term = runReader (interp' term) (Env [])
というわけで、これで完成です。 ラムダ計算のための完全に機能するインタプリタです。
このことについて考えるもう一つの方法は、「どのように実装されているのか」ということです。 その答えは、リーダーモナドは実はすべてのモナドの中で最もシンプルでエレガントなものの1つであるということです。
newtype Reader env a = Reader {runReader :: env -> a}
リーダーとは、単に関数の洒落た呼び方です 私たちはすでに
runReader
を定義しました。では、APIの他の部分はどうなっているのでしょうか? さて、すべての
Monad
はまた
Functor
:
instance Functor (Reader env) where
fmap f (Reader g) = Reader $ f . g
さて、モナドを取得するために
instance Monad (Reader env) where
return x = Reader (\_ -> x)
(Reader f) >>= g = Reader $ \x -> runReader (g (f x)) x
というのは、それほど怖いものではありません。
ask
は本当にシンプルです。
ask = Reader $ \x -> x
一方
local
はそれほど悪くはない。
local f (Reader g) = Reader $ \x -> runReader g (f x)
なるほど、リーダーモナドは単なる関数なんですね。なぜリーダーを持つのでしょうか?いい質問ですね。実は、必要ないんです!
instance Functor ((->) env) where
fmap = (.)
instance Monad ((->) env) where
return = const
f >>= g = \x -> g (f x) x
これらはさらにシンプルです。さらに
ask
は単に
id
であり
local
は関数の順番を入れ替えただけの関数合成です!
関連
-
[解決済み] エラー haskell: スコープ内にありません。どういう意味ですか?
-
[解決済み] Hindley-Milnerのどの部分が理解できないのでしょうか?
-
[解決済み] モナドはエンドファンクタのカテゴリではただのモノイドですが、何か問題でも?
-
[解決済み】Not a Functor/Functor/Applicative/Monadの良い例?
-
[解決済み] RustのtraitとHaskellのtypeclassの違いは何ですか?
-
[解決済み] Haskellにはなぜ "data "と "newtype "があるのですか?重複] [重複] [重複
-
[解決済み] Haskellのリストを参照する際の「@」記号の意味は?
-
[解決済み】Haskellの入門編
-
[解決済み] なぜ依存型でないのか?
-
[解決済み] Haskellの "Just "構文とは?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】なぜパースエラーになるのか?インデント?
-
[解決済み】haskellでリストを逆順にする
-
[解決済み] 一般的に `{- |` で始まるHaskellのコメントは何を意味するのですか?
-
[解決済み] Haskellで "length "関数を使用しない場合のリストの長さ
-
[解決済み] Haskellです。パターンでのパースエラー
-
[解決済み] Haskell タプルをリスト化する?
-
[解決済み] Haskellで大規模設計?[クローズド]
-
[解決済み] Haskellにはなぜ "data "と "newtype "があるのですか?重複] [重複] [重複
-
[解決済み] 制約条件付き特殊化
-
[解決済み] Haskell型とデータコンストラクタ