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

看来我们可以使用 askslocal 来计算相同的结果。

是否有无法从其中任何一个计算得出的结果?

现在,我只能说使用 local 允许我将改变环境的函数 (changeEnv) 和 returns 的函数 (getLength) 分开Reader 的结果。但是,同样可以使用 asks 来实现。

有什么特别的原因我们需要它们吗?

我们有任何 f 的 asks f = local f ask,所以你确实可以 总是根据 localasks。虽然通常你会 将 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 很可能是复杂的。