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:
Code
;Solution
;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).
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 -> bool
andeven : 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]
.
[@@@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.