我正在学习 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)
并使其更通用,以便我可以将其换成单位测试。问题是,因为我们在 userInput
中使用 liftIO
,所以它与 IO 联系在一起
。有没有办法用其他东西替换 i
。/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)
最佳答案
请记住 ReaderT r m a
是 newtype
r -> m a
的包装。具体来说,MonadIO m => ReaderT (IO a) m b
相当于 MonadIO m => IO a -> m b
。所以让我重新表述一下你的问题:
Can you convert
MonadIO m => IO a -> m b
toMonadIO m => m a -> m b
?
答案是否,因为IO a
显示为函数类型的输入。 (有时你会看到人们说“处于负数位置”,这大致意味着与“输入”相同。)这里重要的是转换函数输入的工作方向与转换函数输出的方向相反。
让我们退后一步,考虑一个更一般的情况。如果你有一个函数a -> b
并且您想要将其输出转换为函数 a -> c
,您需要能够转换 b
进入c
s。如果你能给我一个转换 b
的函数进入c
s,我可以将其应用于来自 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
,你必须转换c
进入b
s。如果你能给我一个转换 c
的函数进入b
s,我可以在值进入 b -> a
之前将其应用于值。功能。
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)
(有时您会听到与转换类型的想法相关的词协变和逆变。它们指的是转换器函数可以放在一个函数中的想法。两个方向。函数的输出参数是协变的,输入参数是逆变的。)
回到问题
Can you convert
MonadIO m => IO a -> m b
toMonadIO m => m a -> m b
?
希望你能看到这个问题实际上是在寻求一种方法来转变 m a
进入IO a
。 (您必须将 m a
转换为 IO a
才能将其提供给原始函数。) MonadIO
包含一个方法,liftIO :: IO a -> m a
,其中嵌入 IO
计算成一个“更大”的单子(monad),它可能包含其他效果,但这与我们需要的完全相反。没有其他路可走。
也不应该有。 m a
这是一个一元计算,可以执行各种未知的效果。您不能将任意一元值转换为 IO
而不知道效果是什么。许多(大多数)一元效应并没有直接翻译成 IO
计算;运行 State
例如,计算需要状态的起始值。
关于unit-testing - 是否可以使用类型类将 `ReaderT (IO a) IO a` 更改为 `ReaderT (i a) IO a` ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53972614/