Blog

Let’s fire up QuickCheck and see what we have. Remember that all `AE`

programs famously ran to termination and produced a value. Will the same thing hold true for `ABE`

programs? I suspect you can easily figure the answer, but let’s follow a rigorous process to see why. The process will be useful to us when our languages become more complex.

We need to extend the generator from the `Testing`

chapter to generate `ABE`

elements. Like our parser we simply need to define generators for the new `AST`

elements and add them to the original generator.

Generating `Boolean`

values is identical to generating `Num`

values except we use `choose`

to select among `True`

and `False`

:

```
genBool =
do t <- choose (True,False)
return (Boolean t)
```

Generators for the remaining operators are identical to what we did for `Plus`

and `Minus`

. Specifically, generate the arguments and put them together:

```
genAnd n =
do s <- genABE n
t <- genABE n
return (And s t)
genLeq n =
do s <- genABE n
t <- genABE n
return (Leq s t)
genIsZero n =
do s <- genABE n
return (IsZero s)
genIf n =
do s <- genABE n
t <- genABE n
u <- genABE n
return (If s t u)
```

Remember that the argument `n`

is our size counter that will force the generator to terminate when the new `ABE`

structure reaches a specified size.

Now `genABE`

for generating complete terms:

```
genABE :: Int -> Gen ABE
genABE 0 =
do term <- oneof [genNum,genBool]
return term
genABE n =
do term <- oneof [genNum,(genPlus (n-1))
,(genMinus (n-1))
,(genAnd (n-1))
,(genLeq (n-1))
,(genIsZero (n-1))
,(genIf (n-1))]
return term
```

Note what’s going on here. For the base case, we use `oneof`

to generate either a number or a boolean. For the recursive case, we simply add generators for new terms to the argment to `oneof`

adding them to the `ABE`

generator. That’s it. We’re done.

Our testing functions are identical to those for `AE`

with `AE`

changed to `ABE`

. Literally, that’s all there is to it:

```
testParser :: Int -> IO ()
testParser n = quickCheckWith stdArgs {maxSuccess=n}
(\t -> parseABE (pprint t) == t)
testEval :: Int -> IO ()
testEval n = quickCheckWith stdArgs {maxSuccess=n}
(\t -> (interp $ pprint t) == (eval t))
```

Now we’re ready to go.

Using the `testParser`

function we’ll first test 1000 random `ABE`

terms:

```
testParser 1000
+++ OK, passed 1000 tests.
```

So far, so good. The `ABE`

parser passes 1000 tests of arbitrary structures.

Using the `testEval`

function we’ll now test 1000 random `ABE`

terms:

```
testEval 1000
*** Failed! (after 2 tests):
Exception:
/Users/alex/Documents/Research/books/plih/sources/haskell/abe.hs:123:23-40: Irrefutable pattern failed for pattern (Main.Num v)
IsZero (Boolean False)
```

This result tells us that the `eval`

function fails after 2 tests. Specifically, the term `IsZero (Boolean False)`

cases `eval`

to fail without generating a value. It should be clear why. `IsZero`

expects a number yet it is called on a Boolean value. `IsZero (Boolean False)`

is a *counterexample* for the test and gives us a direction to follow.[^1]