Reader monad 中 local & asks 函数的目的是什么
What is the purpose of local & asks functions in Reader monad
当我尝试熟悉 Haskell 中 Reader monad 的用法时,我注意到 mtl
库中提供了两个类似的函数。
asks :: MonadReader r m => (r -> a) -> m a
local :: MonadReader r m => (r -> r) -> m a -> m a
例如,
import Control.Monad.Reader
changeEnv :: String -> String
changeEnv = ("Prefix " ++)
getLength :: Reader String Int
getLength = do
e <- ask
return $ length e
runReader (local changeEnv getLength) "123"
10
runReader (asks (length . changeEnv)) "123"
10
runReader (asks (length . local changeEnv id)) "123"
10
看来我们可以使用 asks
或 local
来计算相同的结果。
是否有无法从其中任何一个计算得出的结果?
现在,我只能说使用 local
允许我将改变环境的函数 (changeEnv
) 和 returns 的函数 (getLength
) 分开Reader 的结果。但是,同样可以使用 asks
来实现。
有什么特别的原因我们需要它们吗?
我们有任何 f 的 asks f = local f ask
,所以你确实可以
总是根据 local
写 asks
。虽然通常你会
将 asks f
视为 fmap f ask
的 shorthand。
然而,在另一个方向,考虑类似
local changeEnv (do x <- getLength
y <- getReverse
pure (replicate x y))
一般情况下,您如何使用 asks
执行此操作?你必须包括
整个 body 并将环境显式放入其中:
asks ((\s -> replicate (length s) (reverse s)) . changeEnv)
如果你愿意,你随时可以这样做,因为你随时可以翻译
任何 Reader 计算到普通函数中。但是你可能会
最好不要使用 Reader。
这也不意味着 local
是 必需的 - 它只是一个
嵌套阅读器的便利,您可以随时使用 runReader
直接而不是喜欢:
do s' <- asks changeEnv
let x = runReader (do ...) s'
...
我想如果你真的想的话,你可以把它全部塞进 asks
,比如
asks (runReader (do ...) . changeEnv)
但使用 local
似乎更好。
在您的例子中,您可以用涉及 asks
的等效表达式替换 local changeEnv getLength
。但是,为此,您必须检查 getLength
的代码,并在每次代码使用 ask
.
时手动插入 changeEnv
local
的要点是允许转换而不必重写 getLength
的所有代码。想象一个极端的场景,我们有一个非常复杂的 action
而不是 getLength
,可能还调用其他操作,所以我们总共接触了几千行代码。
然后我们找到一个 local f action
我们想要替换。在这种情况下,我们需要重写 action
及其依赖项的所有代码,只是为了在所有正确的地方插入 f
。这很长,而且效率低下,因为通过这种方式我们会多次重新计算 f
。
local
避免了这种情况。当然,严格来说,我们不必使用local
,因为我们可以通过Reader
构造函数(或滥用runReader
)上的模式匹配来打破抽象,这样我们就可以pre-compose f
在正确的位置。然而,在这样做时,我们将重新实现 local
.
此外,请注意 local
不仅适用于简单的 Reader a
monad,而且适用于 class MonadReader
的所有 monad,其中 action
s 很可能是复杂的。
当我尝试熟悉 Haskell 中 Reader monad 的用法时,我注意到 mtl
库中提供了两个类似的函数。
asks :: MonadReader r m => (r -> a) -> m a
local :: MonadReader r m => (r -> r) -> m a -> m a
例如,
import Control.Monad.Reader
changeEnv :: String -> String
changeEnv = ("Prefix " ++)
getLength :: Reader String Int
getLength = do
e <- ask
return $ length e
runReader (local changeEnv getLength) "123"
10
runReader (asks (length . changeEnv)) "123"
10
runReader (asks (length . local changeEnv id)) "123"
10
看来我们可以使用 asks
或 local
来计算相同的结果。
是否有无法从其中任何一个计算得出的结果?
现在,我只能说使用 local
允许我将改变环境的函数 (changeEnv
) 和 returns 的函数 (getLength
) 分开Reader 的结果。但是,同样可以使用 asks
来实现。
有什么特别的原因我们需要它们吗?
我们有任何 f 的 asks f = local f ask
,所以你确实可以
总是根据 local
写 asks
。虽然通常你会
将 asks f
视为 fmap f ask
的 shorthand。
然而,在另一个方向,考虑类似
local changeEnv (do x <- getLength
y <- getReverse
pure (replicate x y))
一般情况下,您如何使用 asks
执行此操作?你必须包括
整个 body 并将环境显式放入其中:
asks ((\s -> replicate (length s) (reverse s)) . changeEnv)
如果你愿意,你随时可以这样做,因为你随时可以翻译 任何 Reader 计算到普通函数中。但是你可能会 最好不要使用 Reader。
这也不意味着 local
是 必需的 - 它只是一个
嵌套阅读器的便利,您可以随时使用 runReader
直接而不是喜欢:
do s' <- asks changeEnv
let x = runReader (do ...) s'
...
我想如果你真的想的话,你可以把它全部塞进 asks
,比如
asks (runReader (do ...) . changeEnv)
但使用 local
似乎更好。
在您的例子中,您可以用涉及 asks
的等效表达式替换 local changeEnv getLength
。但是,为此,您必须检查 getLength
的代码,并在每次代码使用 ask
.
changeEnv
local
的要点是允许转换而不必重写 getLength
的所有代码。想象一个极端的场景,我们有一个非常复杂的 action
而不是 getLength
,可能还调用其他操作,所以我们总共接触了几千行代码。
然后我们找到一个 local f action
我们想要替换。在这种情况下,我们需要重写 action
及其依赖项的所有代码,只是为了在所有正确的地方插入 f
。这很长,而且效率低下,因为通过这种方式我们会多次重新计算 f
。
local
避免了这种情况。当然,严格来说,我们不必使用local
,因为我们可以通过Reader
构造函数(或滥用runReader
)上的模式匹配来打破抽象,这样我们就可以pre-compose f
在正确的位置。然而,在这样做时,我们将重新实现 local
.
此外,请注意 local
不仅适用于简单的 Reader a
monad,而且适用于 class MonadReader
的所有 monad,其中 action
s 很可能是复杂的。