我正在学习Haskell,并且由于this answer(这只是一个echo
程序)的帮助,得到了以下代码。效果很好,但是我想对其进行一些改进,但遇到麻烦了。
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 (IO String)
更改为ReaderT (i String)
并使其更通用,以便将其换出进行单元测试。问题是,因为我们在liftIO
内使用userInput
,它与i
一起 tie IO
。有什么方法可以将liftIO
替换为其他代码,以使以下代码正常工作?
class Monad i => MonadHttp i where
hole :: MonadIO m => i a -> ReaderT (i a) m a
instance MonadHttp IO where
hole = liftIO
newtype MockServer m a = MockServer
{ server :: ReaderT (String) m a }
deriving (Applicative, Functor, Monad, MonadTrans)
instance MonadIO m => MonadHttp (MockServer m) where
-- MockServer m a -> ReaderT (MockServer m a) m1 a
hole s = s -- What goes here?
userInput :: (MonadHttp i, MonadIO m) => ReaderT (i String) m String
userInput = ask >>= hole
echo :: (MonadHttp i, MonadIO m) => ReaderT (i String) m ()
echo = userInput >>= \input ->
((I.liftIO . putStrLn) input)
main = runReaderT echo (return "hello" :: MockServer IO String)
答案 0 :(得分:2)
请记住,ReaderT r m a
是newtype
的{{1}}包装器。具体来说,r -> m a
等效于MonadIO m => ReaderT (IO a) m b
。因此,让我重新表述您的问题:
您可以将
MonadIO m => IO a -> m b
转换为MonadIO m => IO a -> m b
吗?
答案为否,因为MonadIO m => m a -> m b
作为函数类型的输入出现。 (有时您会看到人们说“处于负数位置”,这与“输入”大致相同。)这里重要的是,转换函数输入的方向与转换函数输出的方向相反。
让我们退后一步,考虑一个更一般的情况。如果您具有函数IO a
,并且想要转换其输出以获得函数a -> b
,则需要能够将a -> c
s转换为b
s。如果您可以给我一个将c
转换为b
s的函数,则可以将它们应用于c
函数之后的值。
a -> b
convertOutput :: (b -> c) -- the converter function
-> (a -> b) -- the function to convert
-> (a -> c) -- the resulting converted function
convertOutput f g = \x -> f (g x)
更好地称为convertOutput
。
转换函数的输入的方法与此相反。如果要将函数(.)
转换为函数b -> a
,则必须将c -> a
s转换为c
s。如果您可以给我一个将b
转换为c
s的函数,则可以在将其应用于b
函数之前将其应用于值。
b -> a
(有时您会听到与转换类型有关的 covariance 和 contravariance 一词。它们指的是转换器函数可以合而为一的想法两个方向。函数的输出参数是协变的,而输入则是协变的。)
回到问题,
您可以将
convertInput :: (c -> b) -- the converter function -> (b -> a) -- the function to convert -> (c -> a) -- the resulting converted function convertInput f g = \x -> g (f x)
转换为MonadIO m => IO a -> m b
吗?
希望您能看到这个问题确实是在寻求一种将MonadIO m => m a -> m b
变成m a
的方法。 (您必须将IO a
转换为m a
才能将其提供给原始功能。)IO a
包含单个方法MonadIO
,该方法嵌入了{{ 1}}计算为可能包含其他效果的“更大”单子,但这与我们需要的相反。没有其他选择。
也不应有。 liftIO :: IO a -> m a
是一个可以执行各种未知效果的单子计算。在不知道影响是什么的情况下,无法将任意一元值转换为IO
。而且,许多(大多数)单子效果都无法直接转换为m a
计算;例如,运行IO
计算需要状态的起始值。