代码之家  ›  专栏  ›  技术社区  ›  ctford

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

  •  10
  • ctford  · 技术社区  · 15 年前

    Haskell是一种纯函数语言,这意味着Haskell函数没有副作用。I/O是使用表示I/O计算块的单子实现的。

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

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

    main :: IO ()
    main = putStr "Hello world!"
    

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

    注意,我没有尝试比较I/O操作的返回值。 .

    5 回复  |  直到 15 年前
        1
  •  8
  •   svenningsson ahmed mohamady    15 年前

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

    让我们举个例子。假设我想模拟印刷品。然后我可以像这样对我的IO单子建模:

    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])
    

    compareIO :: IO a -> IO b -> Bool
    compareIO ioA ioB = outA == outB
      where ioA = runIO ioA ioB
    

    有些事情是这种模型无法处理的。例如,输入是很棘手的。但我希望它能适合你的用例。我还应该提到,有更聪明和更有效的方法,以这种方式建模的效果。我选择这种方式是因为我认为这是最容易理解的方式。

    this page 以及其他一些相关文件。

        2
  •  4
  •   Norman Ramsey    15 年前

    在IO monad中,您可以测试IO函数的返回值。在IO monad之外测试返回值是不安全的:这意味着可以这样做,但只会有破坏程序的风险。仅供专家使用。

    main 有类型 IO () ,这意味着“我是一个IO操作,执行该操作时会执行一些I/O,然后返回类型为的值 () () 读作“unit”,这种类型只有两个值:空元组(也写为 ()

    在内部 do 符号

        3
  •  1
  •   Norman Ramsey    15 年前

    你可以测试 一元码 QuickCheck 2 . 我已经很久没有读过这篇论文了,所以我不记得它是否适用于IO操作,或者适用于什么样的一元计算。此外,您可能会发现很难将单元测试表示为QuickCheck属性。不过,作为一个非常满意QuickCheck的用户,我会说它是一个 许多 总比什么都不做或到处乱搞强 unsafePerformIO .

        4
  •  0
  •   yairchu    15 年前

    很抱歉告诉你,你不能这样做。

    unsafePerformIO 使用它。

    Foreign.unsafePerformIO :: IO a -> a
    

        5
  •  0
  •   Community CDub    7 年前

    我喜欢 this answer

    同样,您可以使用断言函数:

    actual_assert :: String -> Bool -> IO ()
    actual_assert _   True  = return ()
    actual_assert msg False = error $ "failed assertion: " ++ msg
    
    faux_assert :: String -> Bool -> IO ()
    faux_assert _ _ = return ()
    
    assert = if debug_on then actual_assert else faux_assert
    

    (您可能需要定义 debug_on 在构建之前由构建脚本构建的单独模块中。此外,如果不是一个标准库的话,这很可能是由一个关于Hackage的包以更精细的形式提供的。。。如果有人知道这样的工具,请编辑此帖子/评论,以便我可以编辑。)

    GHC将足够聪明,完全跳过它发现的任何虚假断言,而实际断言肯定会在失败时使您的程序崩溃。

    依我看,这不太可能满足要求——您仍然需要在复杂场景中进行行为测试——但我想这有助于检查代码所做的基本假设是否正确。