Подтвердить что ты не робот

Test.QuickCheck.Monadic: почему утверждение применяется к Bool, а не Testable a => a

В Тестирование Monadic Code с QuickCheck (Claessen, Hughes 2002), assert имеет тип:

assert :: (Monad m, Testable a) => a -> PropertyM m ()

Однако в Test.QuickCheck.Monadic он имеет тип:

assert :: (Monad m) => Bool -> PropertyM m ()

Почему assert имеет последний тип в библиотеке?

4b9b3361

Ответ 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.