CS691F Programming Languages

| Introduction | Schedule | Software | OCaml Standard Library | Course Libraries |

Functions and Sugar

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

Introduction

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.

Requirements

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.

Directions

1. Define the representation of values

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.

2. Implement and test the evaluator

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.

3. Implement and test boilerplate desugaring

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.

4. Desugar 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.

Hints

REPL

In addition to unit testing, you should write read-eval-print loop (REPL) to explore your programming language. To do so, you'll need to write a printer for your values:

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.

Recursive Functions

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