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:
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,
[@@@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
touch for instance).
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 :
- implement the function
add : peano -> peano -> peano;
- implement the functions
odd : peano -> booland
even : peano -> bool.
type peano = Z | S of peano
let rec add n = function | Z -> n | S m -> S (add n m) let rec odd = function | Z -> false | S n -> even n and even = function | Z -> true | S n -> odd n
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.
open Test_lib open Report 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
val safe_set_result : (unit -> Report.t) list -> unit
let test () = Test_lib.test_function_2_against_solution [%ty : peano -> peano -> peano ] "add" [ (Z, Z) ; (S(Z), S(S(Z))) ]
let test () = Test_lib.test_function_1_against_solution [%ty : peano -> bool ] "odd" [ Z ; S(Z) ; S(S(Z)) ] @ Test_lib.test_function_1_against_solution [%ty : peano -> bool ] "even" [ Z ; S(Z) ; S(S(Z)) ]
Remember that Test_lib internally requires a user-defined sampler
sample_peano : unit -> peano to generate value of type
peano. This sampler
has to be present in the toplevel environment – and not in a module – in order
to be found by the introspection primitives during grading. Therefore,
we define this sampler in a file starting with the annotation
[@@@included] 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:
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.