如何压平IO(IO())?

Jes*_*ieh 4 monads haskell unit-testing 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
Run Code Online (Sandbox Code Playgroud)

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

从根本上说,这个想法是一个monad变换器堆栈,其中一个变换器给你一个IO是HTTP请求还是getLine.我是在考虑这个错误还是有办法做到这一点?

小智 12

使用join.它有类型签名

join :: Monad m => m (m a) -> m a
Run Code Online (Sandbox Code Playgroud)

哪个专攻

join :: IO (IO ()) -> IO ()
Run Code Online (Sandbox Code Playgroud)

你可以使用hoogle找到它.它是一个命令行工具.我们可以按类型签名搜索:

hoogle "IO (IO ()) -> IO ()"
Run Code Online (Sandbox Code Playgroud)

给

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
Run Code Online (Sandbox Code Playgroud)

它有几个功能,正是你想要的.

  • Hoogle还有一个网站:https://www.haskell.org/hoogle/(或https://hoogle.haskell.org/获取最新的alpha版本).您还可以使用集成到Stackage中的Hoogle搜索,可通过https://www.stackage.org/上的搜索栏访问.最后,你也可以使用[Hayoo](https://hayoo.fh-wedel.de/),这是Hoogle的另一种选择,如果你找不到东西的话. (5认同)

Dan*_*ner 10

问题的答案是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
Run Code Online (Sandbox Code Playgroud)

构建返回monadic动作的monadic动作,然后手动组合内部动作,在大多数情况下忽略了monad变换器的全部要点.你应该有一个单独的层,它在内部动作的顶部有一个外部动作的变换器版本,而不是使用ReaderT r Foo (IO a)动作,而不需要手动绑定ReaderT r Foo图层和动作.在IO图层中,您应该使用ReaderT r (FooT IO) a动作,其中只有一个绑定一次处理reader,foo和IO效果.