使用任何"普通"monad [0]的基本方案几乎全面相同.实质上:
do如果你愿意,使用符号,就像你写一个IO函数一样main.<-中的值以获取值"inside",从而导致另一个值"run".let,使其独立于monadic结构.这样做,并且monad描述的额外功能的所有杂乱细节(在这种情况下,传递额外的环境参数)都是自动处理的.
现在,一般的阅读器操作ask和local:
ask是保持环境的一元价值; 在一个do块中你使用它就像你getLine在IOmonad中使用一样.local在Reader monad中提供一个提供新环境和计算的函数,在前者修改的环境中运行后者,然后获取结果并将其放入当前函数中.换句话说,它使用本地修改的environemnt运行子计算."run"函数是创造性命名的runReader,它只是在Reader monad和环境值中进行计算,使用后者运行前者,并在monad之外返回最终结果.
举个例子,这里有一些函数在Reader monad中进行一些无意义的计算,其中环境是一个"最大值",表示何时停止:
import Control.Monad.Reader
computeUpToMax :: (Int -> Int) -> Int -> Reader Int [Maybe Int]
computeUpToMax f x = do
maxVal <- ask
let y = f x
if y > maxVal
then return []
else do zs <- local (subtract y) (computeUpToMax f y)
z <- frob y
return (z:zs)
frob :: Int -> Reader Int (Maybe Int)
frob y = do
maxVal <- ask
let z = maxVal - y
if z == y
then return Nothing
else return $ Just z
Run Code Online (Sandbox Code Playgroud)
要运行它,你可以使用这样的东西:
> runReader (computeUpToMax (+ 1) 0) 9
[Just 8, Just 6, Nothing]
Run Code Online (Sandbox Code Playgroud)
...... 9初始环境在哪里
几乎一模一样的结构可以与其他单子,比如可以使用State,Maybe或者[],虽然在后两种情况下,你通常只需要使用一元的最终结果值,而不是使用"运行"功能.
[0]:正常意味着不涉及编译器魔法,当然最明显的"异常"monad IO.
我认为如果你看看如何在不使用Reader的情况下解决这个问题,然后比较翻译版本,这是最简单的.这是我正在处理的程序的精简示例,其中环境是一组用于更新显示的回调函数.它稍微复杂一点,因为它使用ReaderT而不是Reader,但一切都以基本相同的方式工作.
runProcess :: Env -> State -> Action -> IO State
runProcess env state action = do
newstate <- processAction state action
let ufunc = mainUFunc env -- get the callback to update the display
ufunc newstate -- update the display
return newstate
Run Code Online (Sandbox Code Playgroud)
现在我将其更改为使用Reader monad传递环境.由于代码已经在IO中,因此必须使用monad变换器版本ReaderT.
runProcessR :: State -> Action -> ReaderT Env IO State
runProcessR state action = do
newstate <- lift $ processAction state action
env <- ask -- get the environment from the reader
liftIO $ (mainUFunc env) newstate -- updating is in IO; it needs to be lifted
return newstate
Run Code Online (Sandbox Code Playgroud)
此时,程序的主循环基本上是:
loop :: State -> ReaderT Env IO ()
loop = do
action <- liftIO getAction
if action == EndLoop
then return ()
else do
st' <- processActionR st action
loop st'
mainLoop :: IO ()
mainLoop = do
env <- setUpCallbacks
let st = initState
runReaderT $ loop st
Run Code Online (Sandbox Code Playgroud)
这就是你如何使用Reader.以前用于获取环境参数的每个函数都不再需要.不采取环境的功能可以直接使用,如果它们是monadic可以解除.