如何让Reader和ReaderT一起工作

3

我正在使用ReaderT Monad转换器将配置数据从我的主函数通过执行IO的几个函数传播。需要数据的最终函数不执行任何IO操作。我有一个可行的解决方案:

import Control.Monad.Reader

type Configuration = String

funNoIO :: Reader Configuration String
funNoIO = do
    config <- ask
    return $ config ++ "!"

funIO :: ReaderT Configuration IO String
funIO = do
    config <- ask
    return $ runReader funNoIO config

main :: IO ()
main = do
    c <- runReaderT funIO "configuration"
    print c

但这迫使我在funIO函数中检索配置,而我并不需要它。

我将其修改如下:

funIO' :: ReaderT Configuration IO String
funIO' = do
    v <- funNoIO
    return v

但是它无法编译,我得到了这个错误信息:
Couldn't match type ‘ReaderT Configuration Identity String’
              with ‘Identity (ReaderT Configuration IO String)’
Expected type: Identity (ReaderT Configuration IO String)
  Actual type: Reader Configuration String
In the first argument of ‘runIdentity’, namely ‘funNoIO’
In a stmt of a 'do' block: v <- runIdentity funNoIO

是否有可能在不在中间IO函数中检索配置数据的情况下将其传播到纯函数中?

编辑

我给我的函数参数化了,但我仍然无法在funIO'函数中执行IO操作。例如:

getMessage :: IO String
getMessage = do
    return "message"

funIO' :: MonadIO m => ReaderT Configuration m String
funIO' = do
    m <- getMessage
    v <- funNoIO
    return $ v ++ m

给我报了以下错误:

Couldn't match type ‘IO’ with ‘ReaderT Configuration m’
Expected type: ReaderT Configuration m String
Actual type: IO String

编辑二

我明白了,我只需要使用liftIO

getMessage :: IO String
getMessage = do
    return "message"

funIO' :: MonadIO m => ReaderT Configuration m String
funIO' = do
    m <- liftIO getMessage
    v <- funNoIO
    return $ v ++ m

3
是的。只需让编译器为每个函数推断出最普遍的类型。如果这样做,funIO' 就可以编译了。甚至 funNoIO :: Monad m => ReaderT Configuration m String 也可以工作——因为您从未操纵内部的 monad,所以它可以是任何东西都有意义。然后 funIO' 的一个合理类型是 MonadIO m => ReaderT Configuration m String——MonadIO 约束将来自于定义中任何对 liftIO 的使用。 - user2407038
1
如果您在将funNoIO与依赖于IO的内容混合时使用mmorph方法 https://hackage.haskell.org/package/mmorph-1.0.4,并编写`hoist generalize funNoIO,则可以将其保留为普通Reader。文档中的示例使用了一个与您完全相似的State/StateT示例。hoist`还有许多其他应用。 - Michael
2个回答

3
另一种方法是使用MonadReaderreader方法,再与runReader结合使用:
funIO = reader $ runReader funNoIO

reader.runReader将纯的Reader单子转换为更通用的MonadReader实例。


2

您可以将funNoIOfunIO的类型更改为参数化的单子类型,因为它们没有被使用:

funNoIO :: Monad m => ReaderT Configuration m String
funIO' :: Monad m => ReaderT Configuration m String

要解决编译器错误,您可以将main更改为:

main = do
    c <- runReaderT funIO' "configuration"
    print c

1
你也可以将它们的类型签名设置为 MonadReader Configuration m => m String - Mokosha

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接