是否可以测试Haskell I/O函数的返回值?

ctf*_*ord 10 haskell unit-testing functional-programming

Haskell是一种纯函数式语言,这意味着Haskell函数没有副作用.I/O使用代表I/O计算块的monad实现.

是否可以测试Haskell I/O函数的返回值?

假设我们有一个简单的'hello world'程序:

main :: IO ()
main = putStr "Hello world!"
Run Code Online (Sandbox Code Playgroud)

我是否可以创建一个可以运行的测试工具main并检查I/O monad是否返回正确的"值"?或者monad应该是不透明的计算块这一事实阻止我做这个吗?

注意,我不是要比较I/O操作的返回值.我想比较I/O函数的返回值 - I/O monad本身.

因为在Haskell中I/O被返回而不是执行,所以我希望检查I/O函数返回的I/O计算块并查看它是否正确.我认为这可以允许I/O功能以I/O是副作用的命令式语言中的方式进行单元测试.

sve*_*son 8

我这样做的方法是创建我自己的IO monad,其中包含我想要建模的动作.我会在我的monad中运行我想要比较的monadic计算并比较它们的效果.

我们来举个例子吧.假设我想模拟打印的东西.然后我可以这样建模我的IO monad:

data IO a where
  Return  :: a -> IO a
  Bind    :: IO a -> (a -> IO b) -> IO b
  PutChar :: Char -> IO ()

instance Monad IO where
  return a = Return a
  Return a  >>= f = f a
  Bind m k  >>= f = Bind m (k >=> f)
  PutChar c >>= f = Bind (PutChar c) f

putChar c = PutChar c

runIO :: IO a -> (a,String)
runIO (Return a) = (a,"")
runIO (Bind m f) = (b,s1++s2)
  where (a,s1) = runIO m
        (b,s2) = runIO (f a)
runIO (PutChar c) = ((),[c])
Run Code Online (Sandbox Code Playgroud)

这是我如何比较效果:

compareIO :: IO a -> IO b -> Bool
compareIO ioA ioB = outA == outB
  where ioA = runIO ioA ioB
Run Code Online (Sandbox Code Playgroud)

有些东西是这种模型无法处理的.例如,输入很棘手.但我希望它适合你的用例.我还要提一下,以这种方式建模效果有更聪明有效的方法.我选择了这种特殊方式,因为我觉得这是最容易理解的.

有关更多信息,我可以推荐论文"野兽之美:尴尬小队的功能语义",可以在本页找到,还有其他一些相关论文.