Модульное тестирование неопределенного значения в ленивом выражении в Haskell

Написание модульного теста в 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

но безрезультатно. Сообщает мне did not get expected exception: 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

1 ответ

Решение

Проблема в том, что evaluate не заставляет ваше выражение к NH или даже WHNF 1. Пытаться 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 позволяет запускать оценку thunks, пока аргумент не будет полностью оценен. Обратите внимание, что у него есть NFData (означает "обычные данные формы"), поэтому вы можете получить Generic а также NFData для ваших структур данных.


1 Спасибо за @AlexisKing за указание на то, что evaluate толкает свой аргумент в WNHF, поэтому head $ map (+1) [undefined, 2, 3] действительно вызывает ошибку. В случае take, этого недостаточно, хотя.

Другие вопросы по тегам