Ответ 1
Я думаю, что это было связано с техническими ограничениями, потому что в настоящее время для оценки Testable
с библиотекой Test.QuickCheck
вам нужно использовать один из quickCheck*
, которые очень IO
-центричны. Это происходит потому, что QuickCheck проверяет свойства Testable
путем случайного генерирования возможных входов (по умолчанию 100), пытаясь найти counterexample, что доказывает свойство ложный. Если такой вход не найден, свойство считается истинным (хотя это не обязательно правда, может быть контрпример, который не был протестирован). И чтобы иметь возможность генерировать случайные входы в Haskell, мы придерживаемся монады IO
.
Обратите внимание, что хотя assert
был определен таким общим образом, он используется во всей бумаге только с Bool
. Поэтому автор библиотеки (то же самое из статьи) предпочитал жертвовать общим параметром Testable
для простого Bool
, чтобы не форсировать монаду в этой точке.
И мы видим, что они даже написали оригинальную подпись в качестве комментария в исходный код:
-- assert :: Testable prop => prop -> PropertyM m ()
Также обратите внимание, что несмотря на то, что функция stop
имеет аналогичную подпись:
stop :: (Testable prop, Monad m) => prop -> PropertyM m a
Это не то же, что и функция assert
в документе, поскольку первая будет остановить вычисление в обоих случаях либо с условием True
, либо False
. С другой стороны, assert
остановит только вычисление, если условие False
:
⟦assert True "p⟧ = ⟦p⟧
⟦assert False" p⟧ = {return False}
Мы можем легко написать версию IO
функции assert
из статьи:
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"
И теперь мы можем сделать тест, чтобы увидеть различия между assertIO
и 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
succeeded
и failed
могут быть заменены на True
и False
соответственно. Это было просто показать, что теперь мы не ограничены Bool
, вместо этого мы можем использовать любой Testable
.
И результат:
prop_assert:
*** Не смогли! Утверждение не удалось (после 1 теста):
prop_stop:
+++ ОК, прошло 100 тестов.
Как мы видим, несмотря на то, что первый assertIO
преуспел, prop_assert
не удалось из-за второго assertIO
. С другой стороны, prop_stop
прошел тест, потому что первый stop
преуспел, и вычисление остановилось в этой точке, а не тестирование второго stop
.