を定義する 1 つの方法を見てみましょうReader
。
newtype Reader r a = Reader { runReader :: r -> a }
関数Reader
を取るコンストラクタも同様です。その関数は type の環境を取り、 typer
の結果を返しますa
。
ask = Reader { runReader = \env -> env }
ask = Reader id
このreturn
操作は、環境を無視して値を返すだけです。
return x = Reader { runReader = \_ -> x }
このm >>= n
操作は単純な順序付けを行います。環境を取得m
し、その環境で実行n
してから、同じ環境で実行し、 の結果を渡しますm
。
m >>= n = Reader $ \env -> let
a = runReader m env
in runReader (n a) env
これで、あなたの例を取り上げて、脱糖し、段階的に減らすことができます.
calculateContentLength = do
content <- ask
return (length content)
-- substitute definition of 'ask'
calculateContentLength = do
content <- Reader id
return (length content)
-- substitute definition of 'return'
calculateContentLength = do
content <- Reader id
Reader (\_ -> length content)
-- desugar 'do' into '>>='
calculateContentLength =
Reader id >>= \content -> Reader (\_ -> length content)
-- definition of '>>='
calculateContentLength = Reader $ \env -> let
a = runReader (Reader id) env
in runReader ((\content -> Reader (\_ -> length content)) a) env
-- reduce lambda
calculateContentLength = Reader $ \env -> let
a = runReader (Reader id) env
in runReader (Reader (\_ -> length a)) env
-- definition of 'runReader'
calculateContentLength = Reader $ \env -> let
a = id env
in runReader (Reader (\_ -> length a)) env
-- definition of 'id'
calculateContentLength = Reader $ \env -> let
a = env
in runReader (Reader (\_ -> length a)) env
-- remove redundant variable
calculateContentLength = Reader $ \env
-> runReader (Reader (\_ -> length env)) env
-- definition of 'runReader'
calculateContentLength = Reader $ \env -> (\_ -> length env) env
-- reduce
calculateContentLength = Reader $ \env -> (length env)
calculateContentLength = Reader length
runReader calculateContentLength
これで、 が単に と同じlength
であり、が魔法ではないことが簡単にわかるはずですask
— モナドの>>=
操作は、 で計算を実行するときに暗黙的に環境を渡す関数を構築しますrunReader
。
実際にReader
は、 は純粋な関数の代わりにモナド アクションを使用する で定義されますReaderT
が、その実装の形式は本質的に同じです。