在 Haskell 中编写一个单元测试,当遇到 undefined
时表达式会失败,这有点棘手。我用 HSpec 尝试了以下操作:
module Main where
import Test.Hspec
import Control.Exception (evaluate)
main :: IO ()
main = hspec $ do
describe "Test" $ do
it "test case" $ do
evaluate (take 1 $ map (+1) [undefined, 2, 3]) `shouldThrow` anyException
无济于事。它报告我没有得到预期的异常:SomeException
如果我在 REPL 中计算相同的表达式,我会得到:
[*** Exception: Prelude.undefined
CallStack (from HasCallStack):
error, called at libraries\base\GHC\Err.hs:79:14 in base:GHC.Err
undefined, called at <interactive>:2:20 in interactive:Ghci1
最佳答案
问题是 evaluate
不会强制您的表达式为 NH 甚至 WHNF1。试试x <- evaluate (take 1 $ map (+1) [undefined, 2, 3])
在 GHCi 中 - 它不会给你任何错误!当您粘贴 evaluate (take 1 $ map (+1) [undefined, 2, 3])
时,它会出现的唯一原因是 GHCi 还尝试打印它所得到的结果,为此,它最终会尝试计算表达式。
如果您想查看 thunk 已被评估了多少,您可以随时使用 :sprint
在 GHCi 中:
ghci> x <- evaluate (take 1 $ map (+1) [undefined, 2, 3])
ghci> :sprint x
x = [_]
如您所见,evaluate
还没有将表达式强制到足以实现 x
包含 undefined
。一个快速解决方法是使用 force
将您正在检查的事物评估为正常形式。 .
import Test.Hspec
import Control.Exception (evaluate)
import Control.DeepSeq (force)
main :: IO ()
main = hspec $ do
describe "Test" $ do
it "test case" $ do
evaluate (force (take 1 $ map (+1) [undefined, 2, 3] :: [Int]))
`shouldThrow` anyException
force
允许您触发 thunk 的求值,直到参数被完全求值。请注意,它有一个 NFData
(代表“正常形式数据”)对其进行约束,因此您可能会发现自己导出 Generic
和NFData
用于您的数据结构。
1 感谢@AlexisKing 指出evaluate
确实将其参数插入 WNHF,这就是为什么 head $ map (+1) [undefined, 2, 3]
确实触发了错误。以take
为例,但这还不够。
关于在 Haskell 中对惰性表达式中未定义的值进行单元测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41151927/