使用 reader monad 比直接传递值有什么优势?
What is the advantage of using the reader monad over passing the value directly?
我试图了解何时使用 reader 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
以便能够从任何函数访问它,我应该为此目的使用 reader monad 吗?
与直接将 Env
传递给函数相比,有什么好处吗?
如果我没理解错的话,calculate
和 calculateR
都是纯的(从某种意义上说,它们无法对 env
进行任何更改),并且两者都有Env
在他们的类型签名中告诉他们可能会读取值形式 env
以用于他们的计算。
当您想组合两个函数时,优势就来了。
f :: a -> Reader Env b
g :: b -> Reader Env c
g <=< f
或do
表示法
\a -> do
b <- f a
g b
对
f :: a -> Env -> b
g :: b -> Env -> c
\a e -> g (f a e) e
在后者中,你必须自己不断传递环境,而 monad 实例为你提供了一个为你做这件事的组合。随着涉及的功能越来越多,手动组合将很快变得不必要的冗长和复杂。
Reader 然后也为您提供 local
。
根据我的经验,它在以下任一情况下最有用:
你有一堆函数需要调用图,调用图上的一些东西需要环境,但很多中间函数不需要。然后 reader monad 会为你处理所有的管道。尤其是当你无论如何都在使用一个 monad 转换器堆栈时(尤其是你的堆栈有一个 type 或 newtype 别名)所以你已经在用 monadic 风格编写;在那种情况下,您可能会很晚才意识到您需要环境,更改 monad 堆栈的定义,更改入口点以传递环境,更改使用站点以引用它,而不必触及任何中间代码函数(如果你有堆栈的名称,甚至可能不是它们的类型)。
您正在提供一个接口,其中许多客户端代码将向您传递函数,并且您希望它们能够访问环境,但期望它们中的许多实际上不会需要它。同样,当你已经有了一个 monad 转换器堆栈时,好处会更大,你可以将 reader 插入其中;现有的客户端代码已经是 monadic 风格,可以通过忽略环境而保持不变,同时可以编写使用它的新函数。
但是对于任何小到足以适应堆栈溢出的东西 post,普通函数几乎总是更合适。
我试图了解何时使用 reader 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
以便能够从任何函数访问它,我应该为此目的使用 reader monad 吗?
与直接将 Env
传递给函数相比,有什么好处吗?
如果我没理解错的话,calculate
和 calculateR
都是纯的(从某种意义上说,它们无法对 env
进行任何更改),并且两者都有Env
在他们的类型签名中告诉他们可能会读取值形式 env
以用于他们的计算。
当您想组合两个函数时,优势就来了。
f :: a -> Reader Env b
g :: b -> Reader Env c
g <=< f
或do
表示法
\a -> do
b <- f a
g b
对
f :: a -> Env -> b
g :: b -> Env -> c
\a e -> g (f a e) e
在后者中,你必须自己不断传递环境,而 monad 实例为你提供了一个为你做这件事的组合。随着涉及的功能越来越多,手动组合将很快变得不必要的冗长和复杂。
Reader 然后也为您提供 local
。
根据我的经验,它在以下任一情况下最有用:
你有一堆函数需要调用图,调用图上的一些东西需要环境,但很多中间函数不需要。然后 reader monad 会为你处理所有的管道。尤其是当你无论如何都在使用一个 monad 转换器堆栈时(尤其是你的堆栈有一个 type 或 newtype 别名)所以你已经在用 monadic 风格编写;在那种情况下,您可能会很晚才意识到您需要环境,更改 monad 堆栈的定义,更改入口点以传递环境,更改使用站点以引用它,而不必触及任何中间代码函数(如果你有堆栈的名称,甚至可能不是它们的类型)。
您正在提供一个接口,其中许多客户端代码将向您传递函数,并且您希望它们能够访问环境,但期望它们中的许多实际上不会需要它。同样,当你已经有了一个 monad 转换器堆栈时,好处会更大,你可以将 reader 插入其中;现有的客户端代码已经是 monadic 风格,可以通过忽略环境而保持不变,同时可以编写使用它的新函数。
但是对于任何小到足以适应堆栈溢出的东西 post,普通函数几乎总是更合适。