如何使用 State monad 或 StateT monad 转换器来模拟 IO monad 来测试在基于 IO 的 Monadic 堆栈中运行的函数?

问题描述 投票:0回答:1

在测试中使用
State
作为
IO
的替代品

采用这样一个简单的函数

foo :: IO ()
foo = putStrLn "ciao"

这是不可测试的,因为它会影响终端的状态,我无法为其编写测试。

使用here建议的方法,可以用受类型

m
约束的通用单子
class
重写上述内容,例如名为
IOLike
,这需要实现者,其中之一就是
IO
本身,定义
putStrLn

在代码中这意味着我们定义了这个

class

import Prelude hiding (putStrLn)
class IOLike m where
  putStrLn :: String -> m ()

编写

IO
,

的简单实现
import Prelude hiding (putStrLn)
import qualified Prelude as P (putStrLn)
instance IOLike IO where
  putStrLn = P.putStrLn

State
monad 编写所需的实现,用于在测试中模仿
IO

import MTLPrelude
instance IOLike (State String) where
  putStrLn str = modify' $ \s -> s ++ str

上述代码到位后,我们可以将

foo
的定义更改为

foo :: IOLike m => m ()
foo = putStrLn "ciao"

现在可以通过生产代码在

IO
monad 中使用,

main :: IO ()
main = do
  foo

并在

State
monad 中通过测试代码

main :: IO ()
main = do
  P.putStrLn $ foo `execState` ""

其中

""
是表示终端状态的字符串的初始状态。
P.putStrLn
应该更改为例如通过
QuickCheck
做出的某些断言。


到目前为止一切都很好,但是...

如何使用
State
作为基于
IO
的单子堆栈的替代方案?

但是现在假设我们有另一个函数,

bar
,它意味着在更复杂的 monad 中运行,并在单子堆栈的底部有一个
MonadIO
,例如

bar :: (MonadIO m, MonadReader String m) => m ()
bar = do
  e <- ask
  liftIO $ P.putStrLn $ "ciao" ++ show e

它将在

IO
monad 中运行,

main :: IO ()
main = do
  bar `runReaderT` "env"

再次,我想更改

bar
,以便它可以在任何
IOLike
monad 中运行:

bar :: (IOLike m, MonadReader String m) => m ()
bar = do
  e <- ask
  putStrLn $ "ciao" ++ show e

问题是上面的代码甚至无法在

IO
monad 中运行,

main :: IO ()
main = do
  bar `runReaderT` "env"

因为它会出现

No instance for ‘IOLike (ReaderT String IO)’
错误,更不用说尝试在
State
monad 中运行它了,在测试代码中,
IO

的角色
main :: IO ()
main = do
  P.putStrLn $ bar `runReaderT` "env" `execState` ""

因为这也有错误,

No instance for ‘IOLike (ReaderT String (StateT String Identity))’


很明显,问题在于让

State String
实现
IOLike
并不意味着任何带有
StateT String m
层的单子堆栈也是
IOLike

我想解决方案包括编写一个

instance
来表达任何具有
StateT String
层的单子堆栈都是
IOLike
的想法,但我该怎么做呢?

我尝试将我为

instance
编写的原始
State String
更改为以下内容:

instance (MonadState String m) => IOLike m where
  putStrLn str = modify' $ \s -> s ++ str

但是我得到的是这个错误:

 • The constraint ‘MonadState String m’
     is no smaller than the instance head ‘IOLike m’
 • In the instance declaration for ‘IOLike m’
 • Perhaps you intended to use UndecidableInstances
haskell testing functional-programming monad-transformers io-monad
1个回答
0
投票

如果你保留原来的

instance IOLike (State String)
,但也添加这个,那么它就可以了:

instance (Monad m, IOLike m) => IOLike (ReaderT r m) where
  putStrLn = lift . putStrLn
© www.soinside.com 2019 - 2024. All rights reserved.