Unit-testing the undefined evaluated in lazy expression in Haskell

强颜欢笑 提交于 2020-01-04 09:03:53

问题


Writing a unit test in Haskell where an expression should fail when undefined is encountered is a bit tricky. I tried the following with 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

to no avail. It reports me did not get expected exception: SomeException

If I evaluate the same expression in REPL, I'd get:

[*** 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

回答1:


The problem is that evaluate doesn't force your expression to NH or even WHNF1. Try x <- evaluate (take 1 $ map (+1) [undefined, 2, 3]) in GHCi - it doesn't give you any error! The only reason it does when you paste in evaluate (take 1 $ map (+1) [undefined, 2, 3]) is that GHCi also tries to print the result of what it got and, to do that, it ends up trying to evaluate the expression.

If you want to see how much of a thunk has been evaluated, you can always use :sprint in GHCi:

ghci> x <- evaluate (take 1 $ map (+1) [undefined, 2, 3])
ghci> :sprint x
x = [_]

As you can see, evaluate hasn't forced the expression far enough to realize x contains an undefined. A quick fix is to evaluate the thing you are examining to normal form using 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 lets you trigger the evaluation of thunks until the argument is full evaluated. Note that it has an NFData (stands for "normal form data") constraint on it, so you may find yourself deriving Generic and NFData for your data structures.


1 Thanks for @AlexisKing for pointing out that evaluate does push its argument into WNHF, which is why head $ map (+1) [undefined, 2, 3] does trigger the error. In the case of take, it isn't enough though.



来源:https://stackoverflow.com/questions/41151927/unit-testing-the-undefined-evaluated-in-lazy-expression-in-haskell

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!