![](/img/trans.png)
[英]How to use mapReader from Control.Monad.Reader for reader monad?
[英]How to pass additional arguments to a Control.Monad.Reader instance
我正在阅读Monads之书,然后进入了Reader
monad,其中使用以下示例展示了使用Reader
的动机:
handle :: Config -> Request -> Response
handle cfg req =
produceResponse cfg (initializeHeader cfg) (getArguments cfg req)
为了避免显式传递cfg
参数, Reader
使用如下:
handle :: Request -> Reader Config Response
handle req = do header <- initializeHeader
args <- getArguments req
produceResponse header args
让我感到困惑的部分是cfg
旁边的函数采用附加参数。 我怎样才能做到这一点?
在一个非常天真的尝试中,我尝试了这个:
getArguments :: Reader Config (Request -> Arguments)
produceResponse :: Reader Config (Header -> Arguments -> Response)
我还搜索了几本书和Reader
文档。
这种尝试绝对不幼稚,尽管这不是通常的做事方式。
由于Reader ra
实际上只是一个函数r -> a
在幕后,您对getArguments
和produceResponse
定义本质上是:
getArguments :: Config -> Request -> Arguments
produceResponse :: Config -> Header -> Arguments -> Response
请注意,在这种情况下, Config
始终是第一个参数,因此getArguments req
类的东西将不起作用 - 毕竟, Request
是getArguments
的第二个参数,而不是第一个,因此您不能只将getArguments
应用于req
。
您需要做的是首先将getArguments
应用于Config
。 由于我们实际上是在使用Reader Config ...
而不是简单的函数Config -> ...
,我们通过绑定getArguments
做到这getArguments
:
handle req = do header <- initializeHeader
argumentGetter <- getArguments
-- rest omitted
由于getArguments
是一个Reader Config (Request -> Arguments)
, argumentGetter
将只是一个函数Request -> Arguments
。 使用这样的函数是微不足道的:
handle :: Request -> Reader Config Response
handle req = do header <- initializeHeader
argumentGetter <- getArguments
let args = argumentGetter req
-- rest omitted
然后,您可以继续对生产produceResponse
应用相同的处理produceResponse
,事情就会奏效。
然而,一开始我说这不是通常的做事方式。 考虑这些定义:
getArguments :: Request -> Reader Config Arguments
produceResponse :: Header -> Arguments -> Reader Config Response
如果我们打开Reader
,我们会看到这些实际上只是:
getArguments :: Request -> Config -> Arguments
produceResponse :: Header -> Arguments -> Config -> Response
也就是说, Config
总是作为最后一个参数出现。 这与我们一开始所拥有的一致,其中Config
总是排在第一位。
像这样定义getArguments
和produceResponse
在您的原始示例中效果很好:
args <- getArguments req
- getArguments
施加到req
产生一个Reader Config Arguments
和绑定到args
使得args
一个Arguments
produceResponse header args
- produceResponse
适用于header
和args
产生Reader Config Response
,这非常适合作为最后的陈述中do
块请注意,我们可以对Reader
这样的转换,因为Reader
无论如何都只是一个函数,但一般来说,对于任何Monad m
, m (x -> y)
和x -> my
之间存在相当大的差异,其中后者是“参数化” monadic 操作中最常见的。
例如,考虑readFile :: FilePath -> IO String
。 您提供了一个FilePath
,它为您提供了一个生成文件内容的 IO 操作。 相反,如果它是IO (FilePath -> String)
,它将是一个 IO 操作,它产生一个函数FilePath -> String
- 也就是说,一个纯函数,当给定FilePath
,会产生文件的内容。 然而,由于它是一个纯函数,它不会有任何副作用,所以它实际上无法读取文件,因此这不会真正起作用( getFile
可以做一些疯狂的事情,比如先读取整个文件系统,但我们不要进入那)。
我过去使用的一种选择是制作自定义记录类型
data Context = Context
{ config :: Config
, header :: Header
, args :: Arguments
}
然后,您可以像这样使用它:
foo :: Int -> Reader Context String
foo x = do
h <- asks header -- get the header from the implicit context
a <- asks args
doSomething x h a
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.