如何扁平化IO(IO())?
How to flatten IO (IO ())?
我正在学习 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
。我是不是想错了,还是有什么办法可以做到这一点?
使用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
其中有几个功能正是您想要的。
问题的答案是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
构建 return 单子动作的单子动作,然后手动组合内部动作,在 大多数 情况下忽略了单子变换器的全部要点。你应该有一个单一的层,而不是有两层单子动作,它在内部动作之上有一个外部动作的转换器版本——也就是说,而不是使用需要手动绑定的 ReaderT r Foo (IO a)
动作对于 ReaderT r Foo
层和 IO
层,您应该使用 ReaderT r (FooT IO) a
操作,其中只有一个绑定同时处理 reader、foo 和 IO 效果。
我正在学习 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
。我是不是想错了,还是有什么办法可以做到这一点?
使用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
其中有几个功能正是您想要的。
问题的答案是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
构建 return 单子动作的单子动作,然后手动组合内部动作,在 大多数 情况下忽略了单子变换器的全部要点。你应该有一个单一的层,而不是有两层单子动作,它在内部动作之上有一个外部动作的转换器版本——也就是说,而不是使用需要手动绑定的 ReaderT r Foo (IO a)
动作对于 ReaderT r Foo
层和 IO
层,您应该使用 ReaderT r (FooT IO) a
操作,其中只有一个绑定同时处理 reader、foo 和 IO 效果。