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.