Penso che sia stato a causa di vincoli tecnici perché, al momento di valutare una Testable
con la libreria Test.QuickCheck
, è necessario utilizzare una delle quickCheck*
funzioni, che sono molto IO
-centric. Ciò accade perché QuickCheck verifica le proprietà Testable
generando casualmente gli input possibili (per impostazione predefinita 100), cercando di trovare uno counterexample che provi la proprietà false. Se tale input non viene trovato, si presume che la proprietà sia vera (sebbene ciò non sia necessariamente la verità, potrebbe esserci un controesempio che non è stato testato). E per essere in grado di generare input casuali in Haskell, siamo attaccati alla monade IO
.
Si noti che anche se assert
è stato definito in modo generico, viene utilizzato attraverso tutta la carta solo con Bool
. Quindi l'autore della libreria (lo stesso della carta) ha preferito sacrificare il parametro generico Testable
per un semplice Bool
, per non forzare alcuna monade a questo punto.
E possiamo vedere che essi hanno persino scritto la firma originale come commentare nel source code:
-- assert :: Testable prop => prop -> PropertyM m()
Si noti inoltre che nonostante il fatto che stop
funzione ha una firma simile:
stop :: (Testable prop, Monad m) => prop -> PropertyM m a
È non uguale alla funzione assert
nella carta, poiché il primo sarà interrompe il calcolo in b in altri casi, la condizione è True
o False
. D'altra parte, assert
si fermerà solo il calcolo se la condizione è False
:
⟦affermare vero »p⟧ = ⟦p⟧
⟦affermare Falso» p⟧ = {return False}
possiamo però facilmente scrivere una versione IO
della funzione assert
dalla carta:
import Control.Monad
import Control.Monad.Trans
import Test.QuickCheck
import Test.QuickCheck.Monadic
import Test.QuickCheck.Property
import Test.QuickCheck.Test
assertIO :: Testable prop => prop -> PropertyM IO()
assertIO p = do r <- liftIO $ quickCheckWithResult stdArgs{chatty = False} p
unless (isSuccess r) $ fail "Assertion failed"
E ora possiamo fare un test per vedere le differenze tra assertIO
e stop
:
prop_assert :: Property
prop_assert = monadicIO $ do assertIO succeeded
assertIO failed
prop_stop :: Property
prop_stop = monadicIO $ do stop succeeded
stop failed
main :: IO()
main = do putStrLn "prop_assert:"
quickCheck prop_assert
putStrLn "prop_stop:"
quickCheck prop_stop
Il succeeded
e failed
potrebbe essere sostituito rispettivamente True
e False
,. Era solo per dimostrare che ora non siamo limitati a Bool
, invece possiamo usare qualsiasi Testable
.
e l'uscita è:
prop_assert:
*** non riuscita! Asserzione non riuscita (dopo 1 test):
prop_stop:
+++ OK, superato 100 test.
Come possiamo vedere, nonostante il fatto che il primo assertIO
riuscito, prop_assert
non riuscito a causa della seconda assertIO
. D'altra parte, prop_stop
ha superato il test, perché il primo stop
è riuscito e il calcolo si è fermato a quel punto, non testando il secondo stop
.
Forse i commenti nella fonte saranno di qualche aiuto: [(collegamento)] (https://github.com/nick8325/quickcheck/blob/master/Test/QuickCheck/Monadic.hs#L118-126). Interessante che il commento per "assert" abbia il sig della carta, e anche "stop" abbia fondamentalmente lo stesso sig. – ErikR