There is the forAll
quantifier that returns a property which checks if all test cases pass. Is there a way to define a "there exists" quantifier that returns a property which checks it at least one test case passes?
Testing existence by enumeration would be more reliable: SmallCheck, LeanCheck, FEAT.
If you must use random generation there are some indirect ways in QuickCheck.
expectFailure
seems like a good candidate for negation. However it isn't quite it, as it isn't involutive so you must negate the property in other ways, like not
, if your property is in fact boolean.
exists :: Gen a -> (a -> Bool) -> Property
exists gen prop = once $ expectFailure (forAll gen (not . prop))
This counts crashing as a failure though, which thus makes the test pass even though you might not expect it.
Existential quantification is kind of a disjunction, and QuickCheck has disjoin
. Just make a huge enough disjunction.
exists :: Gen a -> (a -> Property) -> Property
exists gen prop = once $ disjoin $ replicate 10000 $ forAll gen prop
But you get spammed with counterexamples when no good example is found.
Maybe it is better to rewrite the logic of forAll
yourself to avoid the call to counterexample
.
More simply you can always write your own property as a generator to get the proper quantification.
exists :: Gen a -> (a -> Bool) -> Property
exists gen prop = property (exists' 1000 gen prop)
exists' :: Int -> Gen a -> (a -> Bool) -> Gen Bool
exists' 0 _ _ = return False
exists' n gen prop = do
a <- gen
if prop a
then return True
else exists' (n - 1) gen prop
Doing this manually also has the property that if prop
unexpectedly crashes, it is immediately reported as a failure, as opposed to the previous methods.
EDIT
So if you only have a (a -> Property)
instead of (a -> Bool)
it seems much trickier to achieve a good result, because it's non-trivial to check whether a property succeeded. A proper way would be to mess with the internals of QuickCheck, probably something similar to disjoin
. Here is a quick hack
Use
mapResult
to silence the output ofdisjoin
when no (counter)example was found.Override the size parameter because if your witnesses are non-trivial, they will not be found if the size is too small or too large.
import Test.QuickCheck
import Test.QuickCheck.Property as P
-- Retry n times.
exists :: Testable prop => Int -> Gen a -> (a -> prop) -> Property
exists n gen prop =
mapResult (\r -> r {P.reason = "No witness found.", P.callbacks = []}) $
once $
disjoin $
replicate n $ do
a <- gen
return (prop a)
main = quickCheck $ exists 100
(resize 5 arbitrary)
(\x -> (x :: Integer) == 2)
forAll
, for example to write exists gen1 $ \a -> forAll gen2 ...
, exists would have to have signature (Testable prop) => Gen a -> (a -> prop) -> Property
. I've trouble finding out how to do this reading QuickCheck's source code. Would you know how to do it? –
Emmerie prop
you can use .||.
or disjoin
, which ends up resembling what I suggested in the second example about not calling counterexample
. –
Metacenter once $ disjoin $ replicate 10000 $ do { a <- gen ; return (prop a) }
But as I said, it silences exceptions. It turns out you can use mapResult
as suggested by Alexey Romanov to check for exceptions raised at the end of disjoin
. –
Metacenter prop a
is a success of a failure. –
Emmerie disjoin
. Unfortunately I couldn't figure out a good way based on a pure generator (mainly because I'm not familiar enough with the internals of Property
). Somehow once
doesn't have the effect I expected. Sorry if that answer is still unsatisfactory. –
Metacenter I don't know if something like this exist in QuickCheck. Also not convinced it would be helpful, but that's another matter. Since we have:
∃ x . P x <=> ¬ ¬ ∃ x . P x <=> ¬ ∀ x . ¬ P x
I suppose you could set it up so your test-case tests the negation and if it fails it means you have your existential. HTH.
forAll ...
in quickCheck? I've already tried not (forAll gen prop)
but forAll
returns a Property ant not
expects a Bool. –
Emmerie mapResult
: hackage.haskell.org/package/QuickCheck-2.9.2/docs/src/…. –
Duenna © 2022 - 2024 — McMap. All rights reserved.
length a < 0
. So the length should be less than0
. It is clear it will never be satisfied. So the tests will get stuck into an infinite loop where they will keep generating lists in the hope one of them will have a lenght less than0
. Now you cannot make a distinction between: "not satisfied because the condition is rare", or "unsatisifiable". – NoaccountforAll
anyway: you cannot make a distinction between "satisfied because the counter example is rare" and "true". – Emmerie