2016-12-14 1 views
1

L'écriture d'un test unitaire dans Haskell où une expression doit échouer lorsque undefined est rencontré est un peu délicat. J'ai essayé ce qui suit avec HSpec:Test unitaire de l'undefined évalué dans l'expression paresseuse dans Haskell

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 

en vain. Il me rapporte did not get expected exception: SomeException

Si j'évalue la même expression dans REPL, je reçois:

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

Répondre

4

Le problème est que evaluate ne force pas votre expression NH ou même WHNF . Essayez x <- evaluate (take 1 $ map (+1) [undefined, 2, 3]) dans GHCi - il ne vous donne aucune erreur! La seule raison pour laquelle cela se produit lorsque vous collez evaluate (take 1 $ map (+1) [undefined, 2, 3]) est que GHCi essaie également d'imprimer le résultat de ce qu'il a obtenu et, pour ce faire, il essaie d'évaluer l'expression.

Si vous voulez voir à quel point d'un thunk a été évaluée, vous pouvez toujours utiliser :sprint dans GHCi:

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

Comme vous pouvez le voir, evaluate n'a pas forcé l'expression assez loin pour réaliser x contient un undefined. Une solution rapide consiste à évaluer la chose que vous examinez sous une forme normale en utilisant 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 vous permet de déclencher l'évaluation de thunks jusqu'à ce que l'argument est complet évalué. Notez qu'il a une contrainte NFData (signifie "normal form data"), donc vous pouvez trouver vous-même dérivant Generic et NFData pour vos structures de données.


Merci pour @AlexisKing de remarquer que evaluatene fait pousser son argument dans WNHF, ce qui explique pourquoi head $ map (+1) [undefined, 2, 3] ne déclenche l'erreur. Dans le cas de take, cela ne suffit pas cependant.

+0

Cette réponse est bonne, mais je suis confus quand vous dites que 'evaluate' n'évalue pas à WHNF quand [les docs déclarent explicitement qu'il évalue son argument à WHNF] (https://hackage.haskell.org /package/base-4.9.0.0/docs/Control-Exception.html#v:evaluate). –

+0

@AlexisKing Je suis corrigé! Dans tous les cas, WNHF ne suffit pas, d'où la nécessité de "NFData". – Alec

+0

Donc, en fait, le test passe parce qu'il produit une liste avec un seul élément indéfini? Pourquoi la fonction (+1) n'est-elle pas appliquée? –