learn-ocaml

Step 8: Reusing the grader code

This step explains how to separate the grader code, and eventually reuse it in other exercises.

During the grading, the file test.ml is evaluated in an environment that contains notably:

Separating the grader code

It is possible to extend this environment by declaring some other user-defined modules in an optional file depend.txt, located in the exercise directory.

Each declaration in depend.txt is a single line containing the relative path of an .ml or .mli file. The order of the .ml declarations specifies the order in which each module is loaded in the grading environment.

By default each dependency foo.ml is isolated in a module Foo, which can be constrained by the content of an optional signature file foo.mli. Furthermore, an annotation [@@@included] can be used at the beginning of a file foo.ml to denote that all the bindings of foo.ml are evaluated in the toplevel environment (and not in a module Foo).

Dependencies that are not defined at the root of the exercise repository are ignored by the build system: therefore, if you modify them, do not forget to refresh the timestamp of test.ml (using touch for instance).

A complete example

Let’s write an exercise dedicated to Peano numbers. Here is the structure of the exercise:

.
├── exercises
│   ├── index.json
│   └── lib
│   │   ├── check.ml
│   │   └── check.mli
│   ├── peano
│   │   ├── depend.txt
│   │   ├── descr.md
│   │   ├── meta.json
│   │   ├── prelude.ml
│   │   ├── prepare.ml
│   │   ├── solution.ml
│   │   ├── template.ml
│   │   ├── test.ml
│   │   └── tests
│   │       ├── samples.ml
│   │       ├── add.ml
│   │       └── odd_even.ml
│   ├── an-other-exercise
│   │   ├── depend.txt
│   │   │ ...

The exercise peano follows the classical format : prelude.ml, prepare.ml, solution.ml, template.ml and test.ml. It also includes several dependencies (check.ml, samples.ml, add.ml and odd_even.ml) which are declared as follows in depend.txt:

../lib/check.mli
../lib/check.ml    # a comment

tests/samples.ml
tests/add.ml
tests/odd_even.ml

Here is in details the source code of the exercise :

let rec odd = function | Z -> false | S n -> even n and even = function | Z -> true | S n -> odd n


- **test.ml**:
```ocaml
let () =
Check.safe_set_result [ Add.test ; Odd_even.test ]

Note that test.ml is very compact because it simply combines functions defined in separated files.

let safe_set_result tests = set_result @@ ast_sanity_check code_ast @@ fun () -> List.mapi (fun i test -> Section ([ Text (“Question “ ^ string_of_int i ^ “:”) ], test ())) tests


- **../lib/check.mli**:
```ocaml
val safe_set_result : (unit -> Report.t) list -> unit

let sample_peano () = let rec aux = function | 0 -> Z | n -> S (aux (n-1)) in aux (Random.int 42)


Finally, the content of **test.ml** will be evaluated in the following
environment:

```ocaml
val print_html : 'a -> 'b
type peano = Z | S of peano
module Code :
sig
val add : peano -> peano -> peano
val odd : peano -> bool
val even : peano -> bool
end
module Solution :
sig
val add : peano -> peano -> peano
val odd : peano -> bool
val even : peano -> bool
end
module Test_lib : Test_lib.S
module Report = Learnocaml_report
module Check : sig val check_all : (unit -> Report.t) list -> unit end
val sample_peano : unit -> peano
module Add : sig val test : unit -> Report.t end
module Odd_even : sig val test : unit -> Report.t end

In the end, this feature can provide an increased comfort for writing large automated graders and for reusing them in other exercises.