我试图了解何时使用阅读器monad,但我还没有找到一个很好的用法示例。我很确定我对这个话题知之甚少。
考虑这个示例代码:
import Control.Monad.Reader
data Env = Env
{ eInt :: Int
, eStr :: String
}
calculateR :: Reader Env Int
calculateR = do
e <- ask
return $ eInt e
calculate :: Env -> Int
calculate = eInt
main :: IO ()
main = do
let env = Env { eInt = 1, eStr = "hello"}
let a = runReader calculateR env
let b = calculate env
print (a,b)
如果我想传递全局Env
以便能够从任何函数访问它,我应该为此目的使用阅读器monad吗?
与仅将Env
直接传递给函数相比,是否有任何好处?
如果我理解正确,calculate
和calculateR
都是纯粹的(从某种意义上说,他们将无法对env
进行任何更改),并且两者都有{{ 1}}在他们的类型签名中告诉他们可能会读取要在其计算中使用的值Env
。
答案 0 :(得分:14)
当你想要组成两个函数时,就会有好处。
f :: a -> Reader Env b
g :: b -> Reader Env c
g <=< f
或do
符号
\a -> do
b <- f a
g b
VS
f :: a -> Env -> b
g :: b -> Env -> c
\a e -> g (f a e) e
在后者中你必须不断地自己传递环境,而monad实例为你提供了一个组合,为你做。随着涉及更多功能,手册组合将很快变得不必要地冗长和复杂。
然后, Reader也会为您提供local
。
答案 1 :(得分:5)
根据我的经验,在以下任何一种情况下最有用:
你有很多功能都需要深度调用图,而调用图中的一些东西需要环境,但很多中间函数却没有。然后读者monad为你处理所有的管道。特别是当你使用monad变换器堆栈时(特别是对于你的堆栈的类型或新类型别名),所以你已经用monadic风格写了;在这种情况下,您可能很晚才意识到您需要环境,更改monad堆栈的定义,更改入口点以通过环境,更改使用站点以引用它,而不必触摸任何中间的代码函数(如果你有一个堆栈的名字,甚至可能不是它们的类型)。
您正在提供一个界面,其中许多客户端代码将函数传递给您,并且您希望它们能够访问环境,但期望它们中的许多实际上不需要它。再次,当你已经拥有一个monad变换器堆栈时,你可以将读者插入其中,这样做的好处就更大了;现有的客户端代码已经采用monadic风格,可以通过忽略环境来保持不变,同时可以编写使用它的新功能。
但是对于任何小到足以适应堆栈溢出的帖子,普通函数几乎总是更合适。