简体   繁体   English

如何将附加参数传递给 Control.Monad.Reader 实例

[英]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在幕后,您对getArgumentsproduceResponse定义本质上是:

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类的东西将不起作用 - 毕竟, RequestgetArguments的第二个参数,而不是第一个,因此您不能只将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:像这样定义getArgumentsproduceResponse在您的原始示例中效果很好:

  • 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适用于headerargs产生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 mm (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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM