Homework 7
Due: Sunday, October 26 11:59pm
Start with the template code here.
- Replace each
failwith "TODO"with your solution. - Be sure to test your solutions in
utopor in tests alongside your submission. To load your file intoutop, use#use "hw7.ml";;. - Submit your version of
hw7.mlto the HW6 assignment on Gradescope. Only submithw7.ml; NOT any other files or folders. - At the top of the template file, there is a comment for you to write in approximately how many hours you spent on the assignment. Filling this in is optional, but helpful feedback for the instructors.
Q1: Counters
In this problem, we have two implementations of a "counter": mk_counter_1 and mk_counter_2, both of type unit -> (int -> int):
let mk_counter_1 : unit -> (int -> int) =
let c = ref 0 in
fun _ ->
fun x ->
c := !c + x;
!c
let mk_counter_2 : unit -> (int -> int) =
fun _ ->
let c = ref 0 in
fun x ->
c := !c + x;
!c
(), it returns a function of type int -> int. This returned function accumulates a running sum of all inputs seen so far, and outputs that running sum.
For example:
should output 6 (since 1 + 2 + 3 = 6).
However, mk_counter_1 and mk_counter_2 are not identical. In this problem, write a function
mk_counter_1 and mk_counter_2.
Your implementation of distinguish should NOT make reference to mk_counter_1 or mk_counter_2 directly, but only the passed-in counter ctr.
Grading Criteria: Q1
Whether distinguish mk_counter_1 = distinguish mk_counter_2 always returns false.
Q2: Aliasing
In this problem, we want to create a function f : int ref -> int ref -> int that has the following behavior:
when called with c : int ref, holding initial contents v_c : int and d : int ref holding initial contents v_d : int, then:
- After executing
f c d,!cshould bev_c + 1, and!dshould bev_d + 1; and - The return value of
f c dshould returnv_c + v_d + 2.
Q2.1
Design a test:
that checks whetherf meets the above specification when called with c and d.
Your function f_ok should call f on c and d, and test whether the two above properties hold about the return value and the new values of !c and !d --- and return true if and only if this is the case.
Grading Criteria: Q2.1
Whether f_ok precisely implements the two-part specification above.
Q2.2
Now, below is a function, incr2, which is an attempt at implementing this specification:
Even ignoring integer overflows, this specification is false for incr2.
We can see this by specializing our function f_ok to incr2_ok:
Since incr2 is incorrect, there should be a way to call incr2_ok with inputs such that it returns false.
Thus, design a function
(* TODO: Explain why this is a good counter-example. *)
(* Should call incr2_ok e1 e2, for some expressions e1 and e2. *)
let incr2_counter_example () : bool = failwith "TODO"
incr2_ok and makes it return false.
Your function must return the boolean returned by incr2_ok, and it must return false.
Additionally, include a comment above your function that explains why your
incr2_ok returned false for your example.
Grading Criteria: Q2.2
Whether your counter-example demonstrates a call to incr2_ok that causes it to return false.
Q2.3
Design a new function
that does meet the desired specification.Grading Criteria: Q2.3
Whether incr2_fixed meets the specification described above.
Q3: Mutable Data Structures
Let's create a data structure for trees with mutable subtrees:
This is like an ordinary binary tree, but we store references to subtrees, rather than the actual subtree. (This is how algebraic data types are often represented in lower-level languages, like C and Rust, which are defined in terms of pointers.)Q3.1
Every ordinary binary tree can be converted to an mtree. Implement it:
type 'a tree = Leaf of 'a | Branch of 'a tree * 'a tree
let rec mtree_of_tree (t : 'a tree) : 'a mtree = failwith "TODO"
Grading Criteria: Q3.1
Whether mtree_of_tree is implemented correctly: the entire structure of the tree is reflected in the mtree.
Q3.2
We can define a sum function for mtrees holding integers as follows:
let rec mtree_sum (t : int mtree) : int =
match t with
| MLeaf x -> x
| MBranch (l, r) -> mtree_sum !l + mtree_sum !r
Unlike the corresponding sum function on an int tree, there are mtrees where mtree_sum will fail to terminate.
Construct an example mtree below:
Grading Criteria: Q3.2
Whether my_mtree constructs an mtree that causes mtree_sum to loop forever.
Q3.3
While mtree_sum might not terminate, it is still clearly a useful function.
Let's fix the termination issue. Construct a function
None in the case where mtree_sum would not terminate; and otherwise, returns Some v, where v is the sum of the mtree.
If mtree_sum would terminate on your mtree, then mtree_sum_fixed should not return None.
(Hint: think of what caused my_mtree to cause mtree_sum to loop forever.)
Grading Criteria: Q3.3
Whether mtree_sum_fixed always terminates; returns None exactly when mtree_sum would loop forever; and returns Some v otherwise, where v is the sum of the mtree.