Previous Up Next

4.2  Headers and Metaheaders

The informal definition of headers given in the above was actually a simplification. In this section we explore two syntaxes available for headers.

4.2.1  Aliases

Some functions have exceedingly long names. Case in point :

let rec pretentious_drivel x0 f = function [] -> x0
  | x::xs -> pretentious_drivel (f x x0) f xs
(*$T pretentious_drivel
  pretentious_drivel 1 (+) [4;5] = foo 1 (+) [4;5]
  ... pretentious_drivel of this and that...

The constraint that each statement must fit on one line does not play well with very long function names. Furthermore, you known which function is being tested, it’s right there is the header; no need to repeat it a dozen times. Instead, you can define an alias, and write equivalently:

(*$T pretentious_drivel as x
  x 1 (+) [4;5] = foo 1 (+) [4;5]
  ... x of this and that...

... thus saving many keystrokes, thereby contributing to the preservation of the environment. More seriously, aliases have uses beyond just saving a few keystrokes, as we will see in the next sections.

4.2.2  Mutually Tested Functions

Most of the time, a test only pertains to one function. There are times, however, when one wishes to test two functions – or more – at the same time. For instance

let rec even = function 0 -> true
  | n -> odd (pred n)
and odd = function 0 -> false
  | n -> even (pred n)

Let us say that we have the following test:

(*$Q <header>
  Q.small_int (fun n-> odd (abs n+3) = even (abs n))

It involves both even and odd. That question is: "what is a proper header for this test"? One could simply put "even", and thus it would be referenced as being tested in the logs, but odd would not, which is unfair. Putting "odd" is symmetrically unfair. The solution is to put both, separated by a semi-colon:

(*$Q even; odd

That way both functions are referenced in the logs:   37    even   37    odd

and of course the compiler enforces that both of them are actually referenced in each statement of the test. Of course, each of them can be written under alias, in which case the header could be even as x; odd as y.

4.2.3  Testing Functions by the Dozen

Let us come back to our functions foo (after correction) and pretentious_drivel, as defined above.

let rec foo x0 f = function
  [] -> x0 | x::xs -> f x (foo x0 f xs)

let rec pretentious_drivel x0 f = function [] -> x0
  | x::xs -> pretentious_drivel (f x x0) f xs

You will not have failed to notice that they bear more than a passing resemblance to one another. If you write tests for one, odds are that the same test could be useful verbatim for the other. This is a very common case when you have closely related functions, or even several implementations of the same function, for instance the old, slow, naïve, trustworthy one and the new, fast, arcane, highly optimised version you have just written. The typical case is sorting routines, of which there are many flavours.

For our example, recall that we have the following test for foo:

(*$Q foo
  (Q.pair Q.small_int (Q.list Q.small_int)) \
    (fun (i,l)-> foo i (+) l = List.fold_left (+) i l)

The same test would apply to pretentious_drivel; you could just copy-and-paste the test and change the header, but it’s not terribly elegant. Instead, you can just just add the other function to the header, separating the two by a comma, and defining an alias:

(*$Q foo, pretentious_drivel as x
  (Q.pair Q.small_int (Q.list Q.small_int)) \
  (fun (i,l)-> x i (+) l = List.fold_left (+) i l)

This same test will be run once for x = foo, and once for x = pretentious_drivel. Actually, you need not define an alias: if the header is of the form

(*$Q foo, pretentious_drivel

then it is equivalent to

(*$Q foo, pretentious_drivel as foo

so you do not need to alter the body of the test if you subsequently add new functions. A header which combines more than one "version" of a function in this way is called a metaheader.

4.2.4  Metaheaders Unleashed

All the constructs above can be combined without constraints: the grammar is as follows:

    Metaheader  ::=   Binding {";" Binding}
    Binding     ::=   Functions [ "as" ID ]
    Functions   ::=   ID {"," ID}
    ID          ::=   (*OCaml lower-case identifier*)

4.2.5  Header Parameters Injection

coming soon...

Previous Up Next