Due date: Tuesday, September 17th, 11:59PM
You must update the course software to get the support code for this assignment. From the terminal:
$ opam update $ opam upgrade cs691f
In this assignment, you will implement the HOF language (higher order functions), which extends the language of arithmetic expressions with three new features: functions, conditionals, and records. We discussed single-argument functions in class, but HOF supports multi-argument functions. Using records, you will be able to build and manipulate interesting data strucutures. To avoid the trouble of adding boolean values, HOF has an if zero conditional, which branches on integers.
Despite these new features, programming in HOF will be quite tiresome. For example, you'll have to carefully encode simple tests into complicated, nested uses of the if zero test. To address this issue, you'll add booleans and lists to HOF as syntactic sugar.
Submit a file named HOF.ml with the following signature.
(** The type of values. You may pick the representation. *)
type value
(** Evaluates expressions to values. *)
val eval : HOF_syntax.exp -> value
(** Desugars extended HOF to HOF. *)
(* val desugar : HOF_sugar.exp -> HOF_syntax.exp *)
Your code must be thoroughly tested.
The modules HOF_syntax and and HOF_sugar are part of the course support code. The module HOF_util defines parsers and printers that will be helpful for testing and debugging.
First, pick the representation of values.
(** The type of values. You may pick the representation. *)
type value
We recommend using closures to represent function values. You can pick any representation you like for records.
We suggest using environments and closures for this assignment. If you do so, you'll need to define the type of environments and a helper function that consumes environments:
type env
val eval_helper : env -> HOF_syntax.exp -> value
You must write unit tests for eval
and any significant auxilliary
functions you define. Use the TEST
construct we saw in class:
TEST "trivial addition" =
eval (Add (Int 2, Int 3)) = IntVal 5 (* assuming IntVal represents integers *)
You should write several small tests by manually constructed HOF expressions as above. For larger examples, you may find it easier to use the parser and desugaring function.
HOF_sugar extends HOF_syntax . Desugaring booleans and lists requires some thought, but existing features of HOF desugar trivially:
module S = HOF_sugar
let rec desugar (sugared_exp : S.exp) : exp = match sugared_exp with
| S.Id x -> Id x
| S.Int n -> Int n
| S.Add (e1, e2) -> Add (desugar e1, desugar e2)
| S.Sub (e1, e2) -> Sub (desugar e1, desugar e2)
(* etc. *)
You should implement and test these rote cases first. You can now use the provided parser to easily write interesting tests and programs:
let read_eval (str : string) = match HOF_util.parse str with
| HOF_util.Exp exp -> eval (desugar exp)
| HOF_util.ParseError msg -> failwith ("parse error: " ^ msg)
let rec repl () =
print_string "> ";
match HOF_util.parse (read_line ()) with
| HOF_util.Exp exp ->
let v = eval (desugar exp) in
print_string (string_of_value v);
print_newline ();
repl ()
| HOF_util.ParseError msg ->
print_string msg;
print_newline ();
repl ()
let _ =
match Array.to_list Sys.argv with
| [ exe; "repl" ] -> print_string "Press Ctrl + C to quit.\n"; repl ()
| _ -> ()
We expect you to come up with tests that are much more interesting than the one above, especially after you implement booleans and lists.
You must pick your own encoding for booleans and lists. You may assume that expressions are never applied to values of the wrong type. For example, the following expressions are illegal:
let illegal1 = HOF_util.parse "if0 true then 10 else 20"
let illegal2 = HOF_util.parse "
90 && 100"
Later in the course, we will study type checking and type inference to mechanically ensure that all expressions are legal.
In addition to unit testing, you should write
val string_of_value : value -> string
Given this function, you can use the following code to build a REPL:
let rec repl () =
print_string "> ";
match HOF_util.parse (read_line ()) with
| HOF_util.Exp exp ->
let v = eval (desugar exp) in
print_string (string_of_value v);
print_newline ();
repl ()
| HOF_util.ParseError msg ->
print_string msg;
print_newline ();
repl ()
let _ =
match Array.to_list Sys.argv with
| [ exe; "repl" ] -> print_string "Press Ctrl + C to quit.\n"; repl ()
| _ -> ()
Enter cs691f run HOF repl on the terminal to run the REPL.
This language does not have let rec
, which means that
simple attempts to write recursive functions will signal an error:
TEST "cannot recur" =
try
let _ = read_eval "
let fac = lambda (n) . if0 n then 1 else n * fac(n - 1) in
fac(5)" in
false (* expected exception! *)
with Failure "unbound identifier: fac" -> true
But, you can use self application instead:
TEST "self application" =
read_eval "
let make_fac = lambda(self, n).
if0 n then 1 else n * self(self, n - 1) in
let fac = lambda (n) . make_fac(make_fac, n) in
fac(5)"
= IntVal 120