繁体   English   中英

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

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

getArguments :: Config -> Request -> Arguments
produceResponse :: Config -> Header -> Arguments -> Response

请注意,在这种情况下, Config始终是第一个参数,因此getArguments req类的东西将不起作用 - 毕竟, RequestgetArguments的第二个参数,而不是第一个,因此您不能只将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总是排在第一位。

像这样定义getArgumentsproduceResponse在您的原始示例中效果很好:

  • args <- getArguments req - getArguments施加到req产生一个Reader Config Arguments和绑定到args使得args一个Arguments
  • produceResponse header args - produceResponse适用于headerargs产生Reader Config Response ,这非常适合作为最后的陈述中do

请注意,我们可以对Reader这样的转换,因为Reader无论如何都只是一个函数,但一般来说,对于任何Monad mm (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.

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