如何展平IO(IO())?

时间:2018-12-27 02:41:12

标签: unit-testing haskell monads monad-transformers

我正在学习Haskell和monad转换器,我发现自己想使用IO(IO)来扩展为IO()。我确定我做错了什么,但无法确切指出我迷路的地方。

这是我要执行的操作的简化示例。这是实现echo的复杂方法,但它说明了问题。

userInput :: Monad m => ReaderT (IO String) m (IO String)
userInput = ask

echo :: Monad m => ReaderT (IO String) m (IO ())
echo = userInput >>= \input ->  -- unwrap ReaderT to get an IO String
         input >>= (\s ->       -- unwrap IO String to get a String
           putStrLn s)          -- print out the String
         & return               -- rewrap into a ReaderT

main :: IO (IO ())              -- How to turn IO (IO ()) to IO ()?
main = runReaderT echo getLine

在我的实际应用程序中,我有一个Spock应用程序,它向上游服务器发出HTTP请求。 Spock应用程序使用名为SpockCtxT的monad转换器堆栈,我想在堆栈中插入ReaderT来抽象HTTP请求,以便在测试中将其替换为模拟实现。

从根本上讲,这个想法是一个monad转换器堆栈,其中一个转换器给您一个IO,无论是HTTP请求还是getLine。我是在错误地考虑这个问题还是有某种方法可以做到这一点?

2 个答案:

答案 0 :(得分:12)

使用join。它具有类型签名

join :: Monad m => m (m a) -> m a

专门用于

join :: IO (IO ()) -> IO ()

您可以使用hoogle找出答案。它是一个命令行工具。我们可以按类型签名进行搜索:

hoogle "IO (IO ()) -> IO ()"

给予

Control.Monad join :: Monad m => m (m a) -> m a
Control.Composition (.$) :: Monad m => m (m a) -> m a
RIO join :: Monad m => m (m a) -> m a
Universum.Monad.Reexport join :: Monad m => m (m a) -> m a
Stack.Prelude join :: Monad m => m (m a) -> m a
Relude.Monad.Reexport join :: Monad m => m (m a) -> m a
Intro join :: Monad m => m (m a) -> m a
Hledger.Web.Import join :: Monad m => m (m a) -> m a
Data.Edison.Seq concat :: Sequence s => s (s a) -> s a
Data.Edison.Seq.Defaults concatUsingFoldr :: Sequence s => s (s a) -> s a
-- plus more results not shown, pass --count=20 to see more

其中有几个正是您想要的功能。

答案 1 :(得分:9)

该问题的答案为join :: IO (IO ()) -> IO ()。但是,我认为您应该问的问题的答案是liftIO :: IO () -> ReaderT (IO String) IO ()。像这样:

userInput :: MonadIO m => ReaderT (IO String) m String
userInput = ask >>= liftIO -- this liftIO eliminates your need for join

echo :: MonadIO m => ReaderT (IO String) m ()
echo = userInput >>= liftIO . putStrLn -- this liftIO is just so you can use putStrLn in ReaderT

main :: IO ()
main = runReaderT echo getLine
在大多数情况下,

构建返回单调动作的单调动作,然后手动组合内部动作,却忽略了单调变形器的全部要点。不应有两层单子动作,而应有一层在内部动作之上具有外部动作的转换版本–也就是说,而不是使用需要手动绑定的ReaderT r Foo (IO a)动作对于ReaderT r Foo层和IO层,您都应该使用ReaderT r (FooT IO) a动作,其中只有一个绑定同时处理读取器,foo和IO效果。