learn-ocaml icon indicating copy to clipboard operation
learn-ocaml copied to clipboard

Grab student code at a polymorphic type

Open fpottier opened this issue 6 years ago • 4 comments

The functions lookup, lookup_student and lookup_solution in module Test_lib expect an argument of type 'a Ty.ty, that is, a runtime representation of the type 'a. This allows grabbing a value at an arbitrary, monomorphic type. Is there a way of grabbing a value at a polymorphic type?

So far, I have not found a way of doing this. I am forced to work around the problem by grabbing a finite number of monomorphic instances of the polymorphic type I have in mind, but this is unpleasant and does not work at all if I need an unbounded number of monomorphic instances.

(By the way, why are there three functions lookup, lookup_student and lookup_solution? I understand lookup_student versus lookup_solution, but not lookup. Some documentation would be welcome.)

fpottier avatar Feb 19 '19 09:02 fpottier

We have been able to grab functions at a polymorphic type simply by supplying a type such as 'a list -> 'a to the grader. Mostly we use this just to verify that an implementation is indeed polymorphic, since it isn't that useful when used with the functions test_function_n_against_solution, etc.: the outputs in the grading reports are just written as <poly>. If you are manually constructing reports then I don't think there would be an issue, though.

adhameer avatar Nov 09 '19 23:11 adhameer

Hi Aliya, thanks for your comment. You are right, it is possible to look up a value and insist that its type must be polymorphic, as follows:

 T.lookup_student [%ty : 'a -> 'a] "id" ()

However, as far as I understand, this piece of code has monomorphic type ('a -> 'a) lookup. (This is probably imposed by the value restriction.) So, the value that I get can be used at any one monotype, but cannot be used in a polymorphic manner. Is there a way around this problem?

fpottier avatar Nov 10 '19 23:11 fpottier

Ah, I see what you're saying. It looks like, since values are passed around in the datatypes 'a value (introspection.mli) and 'a lookup (test_lib.mli), there probably is not a straightforward way to do this using the lookup functions.

However, something that I think will work for you is using the function test_student_code in Test_lib. https://github.com/ocaml-sf/learn-ocaml/blob/bd002531a99a0f682e06a64e328b8e9fec8932e4/src/grader/test_lib.mli#L549 As I understand it, you can use this function to package (part of) the student code into a module according to a module signature that you provide, and then you have direct access to that module in your grader code. The following grader compiles for me (although I have not actually tested it in the browser):

open Test_lib

module type Has_Id = sig
  val id: 'a -> 'a
end

let my_test () =
  test_student_code
    [%ty: (module Has_Id)]
    (fun (m: (module Has_Id)) ->
      let module M = (val m: Has_Id) in
      let x = M.id 1 in
      let y = M.id "a" in
      [] (* resulting report; here it's just empty *))

let () =
  set_result @@
  ast_sanity_check code_ast my_test

A drawback is that the error messages from test_student_code may be harder for students to understand, since they are in the form of module type mismatches.

adhameer avatar Nov 11 '19 03:11 adhameer

Indeed, this works. Great! It would be nice if this trick could be documented somewhere. Other than that, I believe that this issue can be closed.

fpottier avatar Nov 11 '19 07:11 fpottier