QuickCheck

Files: use91.m
Back to main

# QuickCheck Tutorial 9

## User Defined generators

The default generator will be called only if a user-defined generator does not. The user definded generator must conform to the following format:

    :- func gen_f(type_desc, list(frequency), list({type_desc, list(frequency)}),
                  list(user_gen_type), rnd, rnd) = univ.
    :- mode gen_f(in, in, in, list_skel_in(user_gen_inst), in, out) = out is det.

    :- type user_gen_type
            --->    { type_desc,
                      func(type_desc, list(frequency),
                           list({type_desc, list(frequency)}),
                           list(user_gen_type), rnd, rnd) = univ
                    }.

            %       inst declaration for each user-defined generator
    :- inst user_gen_inst
            =       bound({ ground,
                            func(in, in, in,
                                 list_skel_in(user_gen_inst), in, out) = out is det
                          }).

The last two arguments of type rnd, are the random supply, in & out. (Ignore the rest of arguments.) use91.m shows a user defined generator for type:

            func fff(int) = int
            mode fff(in) = out

    :- module use91.

    :- interface.

    :- use_module io.

    :- pred main(io__state, io__state).
    :- mode main(di, uo) is det.

    %---------------------------------------------------------------------------%

    :- implementation.

    :- import_module int, list, std_util.
    :- import_module qcheck, rnd.

    main -->
            qcheck(qcheck__f(prop1), "user function", 100, [], [],
                   [{type_of(some_function), gen_f}]),
            qcheck(qcheck__f(prop1), "no user function", 100, [], [], []).

    :- func gen_f(type_desc, list(frequency), list({type_desc, list(frequency)}),
                    list(user_gen_type), rnd, rnd) = univ.
    :- mode gen_f(in, in, in, list_skel_in(user_gen_inst), in, out) = out is det.
    gen_f(_, _, _, _, RS, RS) = Univ :-
            Univ = univ(some_function).

    :- func some_function(int) = int.
    :- mode some_function(in) = out is det.
    some_function(X) = Y :-
            Y = 2 * X.

    :- func prop1(func(int)=int) = property.
    :- mode prop1(in(func(in)=out is det)) = out is det.
    prop1(FFF) = FFF(8) `===` (8 * 2).

Sample output:

            Test Description : user function
            Number of test cases that succeeded : 100
            Number of trivial tests : 0
            Number of tests cases which failed the pre-condition : 0
            Distributions of selected argument(s) :

            Test description : no user function
            Falsifiable :
            '<<predicate>>'

The user defined generator is gen_f/6, which ignores the first 4 arguments, and assigns rnd output equal to the input since gen_f doesn't use it. (All that was trivial, basically gen_f/6 ignores all of the inputs.) And it returns some_function/1 in a univ.

    :- pred qcheck(T, string, int, list(list(frequency)),
                   list({type_desc, list(frequency)}),
                   list(user_gen_type), io__state, io__state) <= testable(T).
    :- mode qcheck(in, in, in, in, in, list_skel_in(user_gen_inst), di, uo) is det.
    qcheck(qcheck__f(prop1), "user function", 100, [], [],
                   [{type_of(some_function), gen_f}]),

qcheck/8 is used in use91.m. The last argument takes in the user-defined generators. The format is `list(user_gen_type)`, since there is only 1 user defined generator, the list contains 1 element : `{type_of(some_function), gen_f}`.

type_of(some_function) | describes what type gen_f generates
gen_f | is the actual generator

The invariant function is true only if `FFF(8) `===` (8 * 2)`. The 2nd test shows a random function will "never" do that, but the 1st test with user defined generator will always generate the functions Y = 2 * X

    :- func gen_f(type_desc, list(frequency), list({type_desc, list(frequency)}),
                  list(user_gen_type), rnd, rnd) = univ.
    :- mode gen_f(in, in, in, list_skel_in(user_gen_inst), in, out) = out is det.

Argument number | Type | Description
--- | --- | ---
1 | type_desc | The `type_of` of the current term that the generator is required to generate. It can be useful if the generator is able to generate more than one type, thus the `type_desc` can be used to determine which one.
2 | list(frequency) | Specific frequency for this term; meaningless if the term is not a discriminated union. The user can choose to ignore the setting even if the term is a discriminated union.
3 | list({type_desc, list(frequency)}) | List of general frequency.
4 | list(user_gen_type) | List of user-defined generators.
5 | rnd | Random number supply.
6 | rnd | Random number return. Let return = supply if not used.

Look at `rand_union/6` to find out how to write code that extracts/analyses specific frequency and general frequency. Look at `gen/7` to find out how to write code that extracts/analyses user-defined generators.