Search code examples
haskellquickcheck

why does quickCheck create lists of units


I tried the following from the paper QuickCheck Testing for fun and profit.

prop_revApp xs ys = reverse (xs ++ ys) == reverse xs ++ reverse ys

and it passed even though it should not have. I ran verboseCheck and I see that it is only checking lists of units, i.e.:

Passed:
[(),(),(),(),(),(),(),(),(),(),(),(),(),()]

I was wondering why this was.

I am aware I can fix it by defining the type of the property but was wondering if this was necessary or I was missing something.


Solution

  • The prop_revApp function is quite generic:

    *Main> :t prop_revApp
    prop_revApp :: Eq a => [a] -> [a] -> Bool
    

    If you're just loading the code in GHCi, and run it, yes, indeed, the property passes:

    *Main> quickCheck prop_revApp
    +++ OK, passed 100 tests.
    

    This is because GHCi comes with a set of preferred defaults. For convenience, it'll try to use the simplest type it can.

    It doesn't get much simpler than (), and since () has an Eq instance, it picks that.

    If, on the other hand, you actually try to write and compile some properties, the code doesn't compile:

    import Test.Framework (defaultMain, testGroup)
    import Test.Framework.Providers.QuickCheck2 (testProperty)
    
    import Test.QuickCheck
    
    main :: IO ()
    main = defaultMain tests
    
    prop_revApp xs ys = reverse (xs ++ ys) == reverse xs ++ reverse ys
    
    tests = [
            testGroup "Example" [
                    testProperty "prop_revApp" prop_revApp
               ]
          ]
    

    If you try to run these tests with stack test, you'll get a compiler error:

    test\Spec.hs:11:17: error:
        * Ambiguous type variable `a0' arising from a use of `testProperty'
          prevents the constraint `(Arbitrary a0)' from being solved.
          Probable fix: use a type annotation to specify what `a0' should be.
          These potential instances exist:
            instance (Arbitrary a, Arbitrary b) => Arbitrary (Either a b)
              -- Defined in `Test.QuickCheck.Arbitrary'
            instance Arbitrary Ordering
              -- Defined in `Test.QuickCheck.Arbitrary'
            instance Arbitrary Integer
              -- Defined in `Test.QuickCheck.Arbitrary'
            ...plus 19 others
            ...plus 61 instances involving out-of-scope types
            (use -fprint-potential-instances to see them all)
        * In the expression: testProperty "prop_revApp" prop_revApp
          In the second argument of `testGroup', namely
            `[testProperty "prop_revApp" prop_revApp]'
          In the expression:
            testGroup "Example" [testProperty "prop_revApp" prop_revApp]
       |
    11 |                 testProperty "prop_revApp" prop_revApp
       |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    

    You'll have to give the property a more specific type; e.g.

    tests = [
            testGroup "Example" [
                    testProperty "prop_revApp" (prop_revApp :: [Int] -> [Int] -> Bool)
               ]
          ]
    

    Now the test compiles, but fails:

    $ stack test
    Q56101904-0.1.0.0: test (suite: Q56101904-test)
    
    Example:
      prop_revApp: [Failed]
    *** Failed! Falsifiable (after 3 tests and 3 shrinks):
    [1]
    [0]
    (used seed -7398729956129639050)
    
             Properties  Total
     Passed  0           0
     Failed  1           1
     Total   1           1
    
    Q56101904-0.1.0.0: Test suite Q56101904-test failed
    Test suite failure for package Q56101904-0.1.0.0
        Q56101904-test:  exited with: ExitFailure 1
    Logs printed to console