How to flatten IO (IO ())?

寵の児 提交于 2019-12-23 13:22:46

问题


I'm just learning Haskell and monad transformers and I've found myself with an IO (IO ()) that I'd like to flatten into just IO (). I'm sure that I'm doing something wrong, but can't pinpoint exactly where I'm getting lost.

Here is a simplified example of what I'm trying to do. This is a convoluted way of implementing echo, but it illustrates the problem.

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

In my real application, I have a Spock app that makes HTTP requests to an upstream server. Spock apps use a monad transformer stack called SpockCtxT and I'd like to insert a ReaderT into the stack to abstract the HTTP request so that I can swap it out for a mock implementation in my tests.

Fundamentally, the idea is a monad transformer stack where one of the transformers gives you an IO whether it be an HTTP request or getLine. Am I thinking about this incorrectly or is there some way to do this?


回答1:


The answer to the question as asked is join :: IO (IO ()) -> IO (). But the answer to the question I think you should have asked is liftIO :: IO () -> ReaderT (IO String) IO (). Like this:

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

Building monadic actions that return monadic actions, and then manually combining the inner actions, is in most cases ignoring the whole point of monad transformers. Instead of having two layers of monadic actions, you should have a single layer which has a transformer version of the outer action on top of the inner action -- that is, instead of working with ReaderT r Foo (IO a) actions, which require manual binds for both the ReaderT r Foo layer and the IO layer, you should be working with ReaderT r (FooT IO) a actions, where just one binding handles the reader, foo, and IO effects at once.




回答2:


Use join. It has the type signature

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

which specializes to

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

you can use hoogle to find this out. It is a command-line tool. We can search by type signature:

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

gives

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

which has several functions that are exactly what you want.



来源:https://stackoverflow.com/questions/53939191/how-to-flatten-io-io

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!