这意味着环境无法更新:您只能read来自它(因此得名ReaderT
)。这与 monad 转换器形成对比,例如StateT
它为您提供了一个可以读写的环境。
在 reader monad 内部,您可以使用以下命令访问环境ask http://hackage.haskell.org/package/transformers-0.5.2.0/docs/Control-Monad-Trans-Reader.html#v:ask功能:
ask :: Monad m => ReaderT r m r
在状态单子内部,有一个类似的读取函数,称为get http://hackage.haskell.org/package/transformers-0.5.2.0/docs/Control-Monad-Trans-State-Lazy.html#v:get以及另一个写入状态的函数,称为put http://hackage.haskell.org/package/transformers-0.5.2.0/docs/Control-Monad-Trans-State-Lazy.html#v:put:
get :: Monad m => StateT s m s
put :: Monad m => s -> StateT s m ()
Examples
这是两者的示例用法ReaderT
and StateT
。假设我的底层单子是IO
这样我就可以一路打印东西了。
这里人为的例子是一个数字猜测程序 - 环境只是你试图猜测的一个数字(所以Int
). guess
获取一个数字并检查该数字是否与环境中的数字相同。如果没有,它会在屏幕上打印一条消息。无论哪种情况,它都会返回您的猜测是否成功。
guessReader :: Int -> ReaderT Int IO Bool
guessReader guess = do
actual <- ask
if guess == actual
then return True
else do
lift $ putStrLn ("The number was " ++ show actual)
return False
但是,假设现在您想要一种在猜测后随机更改尝试猜测的数字的方法。然后,由于需要更改环境,因此需要使用StateT
.
import System.Random (randomRIO)
guessState :: Int -> StateT Int IO Bool
guessState guess = do
actual <- get
if guess == actual
then return True
else do
lift $ putStrLn ("The number was " ++ show actual)
newActual <- lift $ randomRIO (0,10)
put newActual
return False
然后,如果您多次运行阅读器版本,请注意您尝试猜测的值永远不会改变。状态版本的情况并非如此,每次您做出错误的猜测时,状态版本都会重置为新数字:
ghci> runReaderT (guessReader 3 >> guessReader 4 >> guessReader 5) 5
The number was 5
The number was 5
True
ghci> evalStateT (guessState 3 >> guessState 4 >> guessState 5) 5
The number was 5
The number was 6
The number was 2
False