如何解决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
)和返回阅读器结果的函数(getLength
)。但是,使用asks
也可以达到同样的效果。
有什么特殊原因需要我们两者吗?
解决方法
对于任何f,我们都有asks f = local f ask
,因此您可以
始终用asks
来写local
。虽然通常你会
将asks f
视为fmap f ask
的简写。
从另一个方向来看,考虑类似
local changeEnv (do x <- getLength
y <- getReverse
pure (replicate x y))
您通常如何使用asks
来做到这一点?您必须包括
整个身体,并明确地将环境放入其中:
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
似乎更好。
对于您而言,您可以用涉及cat ~/.ssh/id_rsa.pub
的等效表达式替换local changeEnv getLength
。但是,为此,您必须检查asks
的代码,并在每次使用getLength
的代码时手动插入changeEnv
。
ask
的重点是允许进行这种转换而不必重写local
的所有代码。想象一下一个极端的情况,其中有一个非常复杂的getLength
而不是getLength
,可能还调用了其他操作,因此我们总共要触摸几千行代码。
然后我们找到一个要替换的action
。在这种情况下,我们只需要在所有正确的位置插入local f action
,就需要重写action
及其依赖关系的所有代码。这很长,而且效率也很低,因为通过这种方式我们多次重新计算f
。
f
避免了这种情况。当然,严格来说,我们不必使用local
,因为我们可以通过local
构造函数上的模式匹配来破坏抽象(或滥用Reader
),以便我们可以预先在正确的位置撰写runReader
。但是,这样做的话,我们将重新实现f
。
此外,请注意,local
不仅适用于简单的local
单子,而且适用于类Reader a
的所有单子,其中MonadReader
可能很复杂一个。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。