Project 1: OCaml Warmup
Due: Tues, Feb 16, 2015 11:59:59pm
Updates
The submit server is now accepting and testing submissions. You will need to download .submit and submit.jar to the directory containing your code. Run java -jar submit.jar to submit everything in the current directory. Check the submit server for test results.
Revised instructions for using ocaml on the GRACE cluster in What to Turn In
Added a clarifying note about add in Part 2: Lambda Calculus
Introduction
This project will help refresh your memory on programming in OCaml by asking you to write two small modules and then develop test cases for them. You may find it helpful to review the OCaml slides from CMSC 330.
Advice
Start early. Submit something as early as possible. You can submit the code skeleton to get partial credit and work out any issues with the submit server.
Make examples and think about what these functions should produce before you start writing code.
Let the type of inputs to a function guide the structure of the function. Together with examples from above, the code will basically write itself.
Part 0: Binary trees
This is not part of the project; it is only here as a warm-up to the rest. Try to do these finger exercises first. If you have trouble, don’t try to proceed to the rest of the project, but rather try to master these simpler concepts first. You do not have submit this code and it will not be graded if you do.
Here’s a type for binary trees:
type 'a bt = |
| Leaf |
| Node of 'a * 'a bt * 'a bt |
Implement the following functions:
(** does the tree contain an element equal to the given one? *) |
val contains : 'a bt -> 'a -> bool |
|
(** produce elements in the left-to-right order they appear in the tree *) |
val to_list : 'a bt -> 'a list |
|
(** zip together two binary trees with same shape; fail if not same shape *) |
val zip : 'a bt -> 'b bt -> ('a * 'b) bt |
|
(** produce tree of same shape, every element is obtained by applying f *) |
val map : 'a bt -> ('a -> 'b) -> 'b bt |
|
(** produce the mirror image of a tree; elements are in reverse order *) |
val rev : 'a bt -> 'a bt |
|
(** list List.fold but for binary trees *) |
val fold : 'a bt -> 'b -> ('a -> 'b -> 'b -> 'b) -> 'b |
A path is a list of directions, which are either left or right, and can describe a path to an element in a binary tree:
type dir = Left | Right |
Write a function the gets the element found by following a given path. This function should fail if the path goes off the end of the tree:
(** get the element found by following given path *) |
val get : 'a bt -> dir list -> 'a |
A binary search tree is binary tree with an ordering on elements such that all elements in the left subtree of a node are less than or equal the value of the node and all elements in the right subtree.
Here’s one possible type for binary search trees:
type 'a bst = ('a -> 'a -> bool) * 'a bt |
Think of the comparison function like (<=).
Implement the following functions:
(** insert the given element into bst *) |
val bst_insert : 'a bst -> 'a -> 'a bst |
|
(** does the bst contain an element equal to the given one? *) |
val bst_contains : 'a bst -> 'a -> bool |
|
(** delete the first occurrence of given element; fail if it doesn't exist *) |
val bst_delete : 'a bst -> 'a -> 'a bst |
Using your implementation of binary search trees, implement a sorting algorithm that consumes a list of elements and produce the same list of elements but in sorted order.
val sort : ('a -> 'a -> bool) -> 'a list -> 'a list |
Part 1: Tries
Your first task is to implement tries, which are data structures designed for fast access to data indexed by sequences such as strings. More precisely, we’ll use the following type for tries:
type ('k, 'v) trie = Trie of 'v option * (('k * ('k, 'v) trie) list) |
Here a (’k, ’v) trie maps lists of keys of type ’k to values of type ’v. For example, if we wanted to map strings to the number of times they appear in a text, we might use a (char list, int) trie. A node in a trie is a pair containing an optional value and a list of (key, trie) pairs. For example, here are some tries:
(* empty trie *) |
Trie (None, []) |
|
(* maps ['a'] to 1 *) |
Trie (None, ['a', Trie (Some 1, [])]) |
|
(* maps ['a'] to 1 and ['b'] to 2 *) |
Trie (None, ['a', Trie (Some 1, []); |
'b', Trie (Some 2, [])]) |
|
(* maps ['a'; 'b'] to 1 *) |
Trie (None, ['a', Trie (None, ['b', Trie (Some 1, [])])]) |
|
(* maps ['a'; 'b'] to 1 and ['a'; 'c'] to 2 *) |
Trie (None, ['a', Trie (None, ['b', Trie (Some 1, []); |
'c', Trie (Some 2, [])])]) |
|
(* maps [] to 1, ['a'] to 2, ['a'; 'b'] to 3 and ['a'; 'c'] to 4 *) |
Trie (Some 1, ['a', Trie (Some 2, ['b', Trie (Some 3, []); |
'c', Trie (Some 4, [])])]) |
You must use the type signature given above to implement tries. You should implement the following functions:
type ('k, 'v) trie |
|
val empty : ('k, 'v) trie |
val is_empty : ('k, 'v) trie -> bool |
val insert : ('k, 'v) trie -> 'k list -> 'v -> ('k, 'v) trie |
val find : ('k, 'v) trie -> 'k list -> 'v |
val delete : ('k, 'v) trie -> 'k list -> ('k, 'v) trie |
val height : ('k, 'v) trie -> int |
val width : ('k, 'v) trie -> int |
val map : ('k, 'v) trie -> ('v -> 'u) -> ('k, 'u) trie |
val iteri : ('k, 'v) trie -> ('k list -> 'v -> unit) -> unit |
val compress : ('k, 'v) trie -> ('k, 'v) trie |
You’ll find the types above in the interface file "trie.mli", and you should put your implementation in "trie.ml". You can find more detailed descriptions of the above functions in the ".ml" file, though probably you can guess how these should behave.
Part 2: Lambda Calculus
Your next task is to implement a few functions related to Lambda calculus, which you learned about in CMSC 330. Below is an interface file "lambda.mli". You must implement the last six of the listed functions in "lambda.ml". (We’ve already implemented unparse for you.)
type expr = |
| Var of string |
| Lam of string * expr |
| App of expr * expr |
|
val unparse : expr -> string |
|
val free_vars : expr -> string list |
val is_alpha : expr -> expr -> bool |
val subst : string -> expr -> expr -> expr |
val beta : expr -> expr option |
val normal_form : expr -> expr |
val expr_of_int : int -> expr |
val add : expr -> expr -> expr |
These functions should do the following:
free_vars e returns a list of the free variables in e. (Duplicates are allowed.)
is_alpha e1 e2 returns true only if the two expressions are alpha equivalent, meaning they are the same expressions up to a consistent renaming of bound variables.
subst x e1 e2 returns a copy of e2 in which free occurrences of x have been replaced by e1. Be sure to implement capture-avoiding substitution, i.e., bound variables should be renamed as needed to avoid capturing free variables under a lambda. For example, subst "x" (Var "y") (Lam("y", Var "x")) should return something equivalent to Lam("z", Var "y") and not Lam("y", Var "y").
beta e applies one step of call-by-value beta reduction to e, or returns None if no reduction is possible, according to the following rules:
Var x and Lam (x, e) do not reduce (they return None).
App (e1, e2)
Returns App(e1’, e2) if e1 reduces to e1’.
Returns App(e1, e2’) if e1 cannot be reduced and e2 reduces to e2’.
Returns subst x e2 e if e1==Lam(x,e) and e2 cannot be reduced.
normal_form e keeps applying beta to e until the expression cannot be reduced any more, and then returns the result.
expr_of_int n returns the Church numeral (see slide 19 at the link above) encoding of a non-negative OCaml integer.
add e1 e2 adds two Church numerals (slide 22) and produces a Church numeral representing their sum. Note: there are several different—
but equivalent— terms that can represent the sum of two Church numerals. Your implementation may produce any one of them.
Part 3: Unit Testing
Your last task is to write unit tests for parts 1 and 2 above, using OUnit. Put your test cases in the file "test.ml", and be sure to add your test cases to the suites suite_trie and suite_lambda, as appropriate. We will grade this part of the project by running your test cases against various correct and incorrect implementations. A test case will only be counted as correct if it passes a correct implementation and fails at least one incorrect implementation.
What to Turn In
Put all your code in the code skeleton we have supplied, and upload your solution to the submit server. You can either upload your solution via the web interface, or you can run java -jar submit.jar in the project directory.
Important note: On GRACE you should use the version of OCaml installed in the public directory for this course. It is version 4.02.3 and has the ocamlfind and ounit libraries pre-installed. To access this version of ocaml, run the following command after logging in to grace.umd.edu:
$ source /afs/glue.umd.edu/class/spring2016/cmsc/430/0101/public/opam-init/init.csh |
You can confirm you have the new version of OCaml as follows:
$ ocaml -version |
The OCaml toplevel, version 4.02.3 |
If you want to make so that you always use the new version of OCaml, you can create (or edit, if it exists) the file .startup.tty in your home directory and add the source command from above. Running the following will add this line to that file. After doing this, the newer version of OCaml will be available when you log in without having to the source thing every time:
$ touch .startup.tty |
$ echo 'source /afs/glue.umd.edu/class/spring2016/cmsc/430/0101/public/opam-init/init.csh' >> .startup.tty |
To test this, log out, log back in. If you run ocaml, you should see 4.02.3.
You should now be able to download, unpack, and compile the code skeleton for Project 1 on GRACE. Here’s a sample, showing how to do it:
$ curl -s http://www.cs.umd.edu/class/spring2016/cmsc430/p1.tar.gz | tar -xz |
$ cd p1/ |
$ make |
ocamlbuild main.native -pkgs oUnit |
Finished, 7 targets (0 cached) in 00:00:00. |
$ ./main.native |
<....testing output....> |
Ran: 1 tests in: 0.00 seconds. |
OK |
Please post any questions you have about this on Piazza. You are welcome to install OCaml on your own machine, though we can’t provide much support for that. We do recommend using opam to manage the installation process if you choose to do that. You can use opam to install both OCaml and OUnit.
Academic Integrity
The Campus Senate has adopted a policy asking students to include the following statement on each assignment in every course: “I pledge on my honor that I have not given or received any unauthorized assistance on this assignment.” Consequently your program is requested to contain this pledge in a comment near the top.
Please carefully read the Academic Integrity section
of the course syllabus. Any evidence of impermissible
cooperation on projects, use of disallowed materials or resources, or
unauthorized use of computer accounts, will be submitted to the
Student Honor Council, which could result in an XF for the course, or
suspension or expulsion from the University. Be sure you understand
what you are and what you are not permitted to do in regards to
academic integrity when it comes to project assignments. These
policies apply to all students, and the Student Honor Council does not
consider lack of knowledge of the policies to be a defense for
violating them. Full information is found in the course
syllabus—