[英]How to pass additional arguments to a Control.Monad.Reader instance
I'm reading the Book of Monads and I got to the Reader
monad where the motivation for using Reader
is presented using this example:我正在阅读Monads之书,然后进入了
Reader
monad,其中使用以下示例展示了使用Reader
的动机:
handle :: Config -> Request -> Response
handle cfg req =
produceResponse cfg (initializeHeader cfg) (getArguments cfg req)
To avoid explicitly passing the cfg
argument the Reader
is used as following:为了避免显式传递
cfg
参数, Reader
使用如下:
handle :: Request -> Reader Config Response
handle req = do header <- initializeHeader
args <- getArguments req
produceResponse header args
The part that confuses me are the functions which beside the cfg
take additional parameters.让我感到困惑的部分是
cfg
旁边的函数采用附加参数。 How can I achieve that?我怎样才能做到这一点?
In a pretty naive attempt I tried this:在一个非常天真的尝试中,我尝试了这个:
getArguments :: Reader Config (Request -> Arguments)
produceResponse :: Reader Config (Header -> Arguments -> Response)
I also searched in a couple of books and in the Reader
docs.我还搜索了几本书和
Reader
文档。
That attempt is definitely not naive, though it is not the usual way of doing things.这种尝试绝对不幼稚,尽管这不是通常的做事方式。
Since Reader ra
is really just a function r -> a
behind the scenes, your definitions of getArguments
and produceResponse
are essentially:由于
Reader ra
实际上只是一个函数r -> a
在幕后,您对getArguments
和produceResponse
定义本质上是:
getArguments :: Config -> Request -> Arguments
produceResponse :: Config -> Header -> Arguments -> Response
Notice that in this case Config
is always the first parameter, so things like getArguments req
will not work - after all, Request
is the second parameter of getArguments
, not the first one, so you can't just apply getArguments
to req
.请注意,在这种情况下,
Config
始终是第一个参数,因此getArguments req
类的东西将不起作用 - 毕竟, Request
是getArguments
的第二个参数,而不是第一个,因此您不能只将getArguments
应用于req
。
What you need to do is to first apply getArguments
to Config
.您需要做的是首先将
getArguments
应用于Config
。 Since we're actually working with a Reader Config ...
and not a simple function Config -> ...
, we do that by binding getArguments
:由于我们实际上是在使用
Reader Config ...
而不是简单的函数Config -> ...
,我们通过绑定getArguments
做到这getArguments
:
handle req = do header <- initializeHeader
argumentGetter <- getArguments
-- rest omitted
Since getArguments
is a Reader Config (Request -> Arguments)
, argumentGetter
will simply be a function Request -> Arguments
.由于
getArguments
是一个Reader Config (Request -> Arguments)
, argumentGetter
将只是一个函数Request -> Arguments
。 Using such a function is then trivial:使用这样的函数是微不足道的:
handle :: Request -> Reader Config Response
handle req = do header <- initializeHeader
argumentGetter <- getArguments
let args = argumentGetter req
-- rest omitted
You could then go on and apply the same treatment to produceResponse
and things would work.然后,您可以继续对生产
produceResponse
应用相同的处理produceResponse
,事情就会奏效。
However, at the start I said that this is not the usual way of doing things.然而,一开始我说这不是通常的做事方式。 Consider these definitions:
考虑这些定义:
getArguments :: Request -> Reader Config Arguments
produceResponse :: Header -> Arguments -> Reader Config Response
If we unwrap the Reader
, we'll see that these are actually just:如果我们打开
Reader
,我们会看到这些实际上只是:
getArguments :: Request -> Config -> Arguments
produceResponse :: Header -> Arguments -> Config -> Response
That is, Config
always comes as the last parameter.也就是说,
Config
总是作为最后一个参数出现。 This is in contract to what we had at the start, where Config
always came first.这与我们一开始所拥有的一致,其中
Config
总是排在第一位。
Defining getArguments
and produceResponse
like this works nicely in your original example:像这样定义
getArguments
和produceResponse
在您的原始示例中效果很好:
args <- getArguments req
- getArguments
applied to req
produces a Reader Config Arguments
and binding that to args
makes args
an Arguments
args <- getArguments req
- getArguments
施加到req
产生一个Reader Config Arguments
和绑定到args
使得args
一个Arguments
produceResponse header args
- produceResponse
applied to header
and args
produces a Reader Config Response
, which fits nicely as the last statement in the do
block produceResponse header args
- produceResponse
适用于header
和args
产生Reader Config Response
,这非常适合作为最后的陈述中do
块Note that we could do a transformation like this for Reader
since Reader
is just a function anyway, but in general for any Monad m
, there's a quite a bit of difference between m (x -> y)
and x -> my
, with the latter being the most common for "parameterized" monadic operations.请注意,我们可以对
Reader
这样的转换,因为Reader
无论如何都只是一个函数,但一般来说,对于任何Monad m
, m (x -> y)
和x -> my
之间存在相当大的差异,其中后者是“参数化” monadic 操作中最常见的。
For example, consider readFile :: FilePath -> IO String
.例如,考虑
readFile :: FilePath -> IO String
。 You provide a FilePath
and it gives you an IO action that produces the contents of the file.您提供了一个
FilePath
,它为您提供了一个生成文件内容的 IO 操作。 If instead it were IO (FilePath -> String)
, it would be an IO action that produces a function FilePath -> String
- that is, a pure function that, when given a FilePath
, produces the contents of a file.相反,如果它是
IO (FilePath -> String)
,它将是一个 IO 操作,它产生一个函数FilePath -> String
- 也就是说,一个纯函数,当给定FilePath
,会产生文件的内容。 However, since it's a pure function it can't have any side effects, so it can't actually read the file and thus this would not really work ( getFile
could do crazy stuff like read the whole filesystem first, but let's not get into that).然而,由于它是一个纯函数,它不会有任何副作用,所以它实际上无法读取文件,因此这不会真正起作用(
getFile
可以做一些疯狂的事情,比如先读取整个文件系统,但我们不要进入那)。
One option I used in the past is to make a custom record type我过去使用的一种选择是制作自定义记录类型
data Context = Context
{ config :: Config
, header :: Header
, args :: Arguments
}
Then, you can use it like this:然后,您可以像这样使用它:
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.