Unit testing 是否可以使用TypeClass将`ReaderT(IO a)IO a`更改为`ReaderT(i a)IO a`?
我正在学习Haskell,由于来自的帮助,我得到了以下代码,这只是一个Unit testing 是否可以使用TypeClass将`ReaderT(IO a)IO a`更改为`ReaderT(i a)IO a`?,unit-testing,haskell,monads,typeclass,monad-transformers,Unit Testing,Haskell,Monads,Typeclass,Monad Transformers,我正在学习Haskell,由于来自的帮助,我得到了以下代码,这只是一个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 :: 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字符串)
更改为ReaderT(I字符串)
,并使其更通用,这样我就可以将其替换为单元测试。问题是,因为我们在userInput
中使用liftIO
,它将i
与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)
请记住,这是r->ma
的newtype
包装。具体来说,monadiom=>ReaderT(ioa)mb
相当于monadiom=>ioa->mb
。因此,让我重新表述你的问题:
你能把monadiom=>ioa->mb
转换成monadiom=>ma->mb
吗
答案是否,因为IO a
显示为函数类型的输入。(有时你会看到人们说“处于负位置”,这与“输入”的意思大致相同。)这里重要的是转换函数输入的工作方向与转换函数输出的工作方向相反
让我们退一步,考虑一个更一般的情况。如果您有一个函数
a->b
,并且希望将其输出转换为函数a->c
,则需要能够将b
s转换为c
s。如果你能给我一个将b
s转换成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)
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)
convertOutput
通常被称为(.)
转换函数的输入的工作方式正好相反。如果要将函数b->a
转换为函数c->a
,则必须将c
s转换为b
s。如果你能给我一个将c
s转换为b
s的函数,我可以在值进入b->a
函数之前将其应用于值
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)
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)
(偶尔你会听到“协方差”和“逆变”这两个词与转换类型的概念有关。它们指的是转换函数可以朝两个方向中的一个方向运行。函数的输出参数是协变的,输入参数是逆变的。)
回到问题上来 你能把
monadiom=>ioa->mb
转换成monadiom=>ma->mb
吗
希望您能看到,这个问题实际上是在寻求一种方法,将ma
转换为ioa
。(您必须将ma
转换为ioa
以将其提供给原始函数。)MonadIO
包含一个方法,liftIO::ioa->ma
,它将IO
计算嵌入到一个“更大”的monad中,该monad可能包含其他效果,但这与我们需要的正好相反。没有别的路可走
也不应该有
ma
这是一个一元计算,可以执行各种未知效果。在不知道效果的情况下,不能将任意一元值转换为IO
。而且许多(大多数)一元效应并没有直接转化为IO
计算;例如,运行状态
计算需要状态的起始值。MonadIO
是一类monad,其中可以嵌入IO
计算liftIO
就是这样做的:将IO
计算提升到您的MonadIO
。如果您想用更通用的东西替换IO
,您还需要用更通用的东西替换MonadIO
。谢谢您的帮助!我想我感到困惑的是,我实际上不想改变m
,在这种情况下,它是IO
。我想更改I
,它也是IO
。但是m
是MonadIO
,而不是i
。我想这里我缺少了一些东西。为什么模拟需要是一个ReaderT字符串IO
,而不仅仅是IO
?我只是在看这篇文章:哇,非常感谢你提供了详细易懂的答案!由于这种方法是不可能的,您是否会有一个建议,说明如何模拟用户输入进行单元测试?这取决于您正在尝试做什么!如果您只想提供一些固定的输入,可以将return“myCannedInput”
作为读取器的IO字符串
参数。如果您需要在每次执行操作时更改输入,则必须强制执行,方法是在操作的基础上构建一个状态机。