unit-testing - 是否可以使用类型类将 `ReaderT (IO a) IO a` 更改为 `ReaderT (i a) IO a` ?

标签 unit-testing haskell monads typeclass monad-transformers

我正在学习 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 to MonadIO 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 to MonadIO 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/

相关文章:

haskell - 为什么 FingerTrees 的使用不足以实现稳定的实现?

scala - 如果 A 和 B 是单子(monad),如何将 A[B[C]] 转换为 B[A[C]]?

haskell - 我怎样才能编写一个也可以进行错误处理的状态单子(monad)?

java - 使用 PowerMock 模拟私有(private)方法

javascript - 测试 jQuery 代码的最先进方法是什么?

ios - 无法在测试中将 App Delegate 转换为 App Delegate

python - Pytest:将一个 fixture 传递给另一个

haskell - 全部约束

haskell - 对元组的所有值有任何内置的 fmap-of-sorts 吗?

scala - 使用 scalaz Monad 的示例