如何将附加参数传递给 Control.Monad.Reader 实例
How to pass additional arguments to a Control.Monad.Reader instance
我正在阅读 Book of 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
文档。
我过去使用的一个选项是制作自定义记录类型
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
这种尝试绝对不幼稚,虽然这不是通常的做事方式。
由于 Reader r a
实际上只是幕后的一个函数 r -> a
,您对 getArguments
和 produceResponse
的定义本质上是:
getArguments :: Config -> Request -> Arguments
produceResponse :: Config -> Header -> Arguments -> Response
请注意,在这种情况下,Config
始终是第一个参数,因此 getArguments req
之类的东西将不起作用 - 毕竟,Request
是 [=17= 的第二个参数], 不是第一个,所以你不能只将 getArguments
应用到 req
.
您需要做的是先将getArguments
应用到Config
。由于我们实际上是在使用 Reader Config ...
而不是简单的函数 Config -> ...
,因此我们通过绑定 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
应用相同的处理,一切都会奏效。
但是,一开始我就说过这不是通常的做事方式。考虑这些定义:
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
,这非常适合作为 [=52= 中的最后一个语句]块
请注意,我们可以对 Reader
进行这样的转换,因为 Reader
只是一个函数,但通常对于任何 Monad m
,都有很大的不同在 m (x -> y)
和 x -> m y
之间,后者是“参数化”单子操作最常见的。
例如,考虑 readFile :: FilePath -> IO String
。您提供一个 FilePath
,它会为您提供一个生成文件内容的 IO 操作。相反,如果它是 IO (FilePath -> String)
,它将是一个生成函数 FilePath -> String
的 IO 操作——也就是说,一个纯函数,当给定一个 FilePath
时,它会生成一个文件的内容。然而,由于它是一个纯函数,它不会有任何副作用,所以它实际上不能 读取 文件,因此这不会真正起作用(getFile
可以疯狂的东西,比如首先读取整个文件系统,但让我们不要进入那个)。
我正在阅读 Book of 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
文档。
我过去使用的一个选项是制作自定义记录类型
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
这种尝试绝对不幼稚,虽然这不是通常的做事方式。
由于 Reader r a
实际上只是幕后的一个函数 r -> a
,您对 getArguments
和 produceResponse
的定义本质上是:
getArguments :: Config -> Request -> Arguments
produceResponse :: Config -> Header -> Arguments -> Response
请注意,在这种情况下,Config
始终是第一个参数,因此 getArguments req
之类的东西将不起作用 - 毕竟,Request
是 [=17= 的第二个参数], 不是第一个,所以你不能只将 getArguments
应用到 req
.
您需要做的是先将getArguments
应用到Config
。由于我们实际上是在使用 Reader Config ...
而不是简单的函数 Config -> ...
,因此我们通过绑定 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
应用相同的处理,一切都会奏效。
但是,一开始我就说过这不是通常的做事方式。考虑这些定义:
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
,这非常适合作为 [=52= 中的最后一个语句]块
请注意,我们可以对 Reader
进行这样的转换,因为 Reader
只是一个函数,但通常对于任何 Monad m
,都有很大的不同在 m (x -> y)
和 x -> m y
之间,后者是“参数化”单子操作最常见的。
例如,考虑 readFile :: FilePath -> IO String
。您提供一个 FilePath
,它会为您提供一个生成文件内容的 IO 操作。相反,如果它是 IO (FilePath -> String)
,它将是一个生成函数 FilePath -> String
的 IO 操作——也就是说,一个纯函数,当给定一个 FilePath
时,它会生成一个文件的内容。然而,由于它是一个纯函数,它不会有任何副作用,所以它实际上不能 读取 文件,因此这不会真正起作用(getFile
可以疯狂的东西,比如首先读取整个文件系统,但让我们不要进入那个)。