Specifications.Assign5
This assignment is due on Thursday 3/6 by 8:00PM. You should put all of your solutions in assign5/lib/assign5.ml
. See the file test/test_assign5.ml
for example behavior of each function.
These problems come the textbook OCP.
In this problem we'll be looking at a common pattern for representing expressions with metadata. We'll look at arithmetic expressions with addition, subtraction, multiplication, division, and exponentiation.
Expressions are defined mutually recursively so that all expressions, including subexpressions, carry metadata of a given type parameter.
Note the very cool use of the and
keyword, which allows use to define mutually recursive types (and functions). We'll be implementing an evaluator for expressions with metadata, and for which errors cases are handled using the result
type.
There are two kinds of errors that could occur when evaluating an arithmetic expression of this form:
DivByZero
error.NegExp
error.Like expressions, errors carry metadata. As an example, we can imagine expressions and errors carrying data describing the line number at which an expression/error occurs, so that we can present a better error message to the user.
Implement the function eval
so that eval e
is Ok n
if e
has no division-by-zero or negative-exponent errors and has the value n
. The expression eval e
should be Error {error;meta}
otherwise, where error
describes the kind of error in e
and meta
is the metadata of the leftmost division expression whose right-hand side evaluates to zero, or the leftmost exponentiation expression whose right-hand side evaluates to a negative number.
You can implement this function however you'd like but we recommend trying to use the monadic let*
notation, since the solution done in this way is quite clean.
We've included the function guard
, which may be helpful. It can be used as follows:
let ( let* ) = Result.bind
let guard b error = if b then Error error else Ok ()
let check x_res error =
let* x = x_res in
let* _ = guard (x = 0) error in
Ok x
let _ = assert (check (Ok 2) () = Ok 2)
let _ = assert (check (Error ()) () = Error ())
let _ = assert (check (Ok 0) () = Error ())
Implement the function prefix
so that prefix k l
is the list consisting of the first k
elements of l
. It should raise a ListTooShort
exception if k
is greater than the length of l
, and should raise an InvalidArg
exception if k
is negative.
val prefix_res : int -> 'a list -> ('a list, prefix_error) Stdlib.result
Implement the function prefix_res
so that prefix k l
is Ok p
where p
is the list consisting of the first k
elements of l
, given k
is at most the length of l
and k
is nonnegative. It should return an error otherwise (I'll let you determine what kind of error). Hint: Don't reimplement the function.
A double-ended queue, or dequeue, is a list-like data structure for which is possible to pop from and push to both the front and back. For our purposes a dequeue implements the following signature.
module type DEQUEUE = sig ... end
module ListDequeue : DEQUEUE with type 'a t = 'a list
module DoubleListDequeue : DEQUEUE with type 'a t = 'a list * 'a list
You need to implement two modules with this signature.
ListDequeue
, which takes t
to be 'a list
, and is implemented in the expected way. For this implementation push_back
and pop_back
will be inefficient.DoubleListDequeue
, which takes t
to be a 'a list * 'a list
.For the DoubleListDequeue
implementation, we keep track of a front
and back
list, which we can pop from and push to. This means the the back
list holds elements in reverse. In the case that the front
list is empty when pop_front
is called, the front
and back
list should be balanced. This means front
and back
after the call should have the property that
List.length back - List.length front <= 1
The same is true for a call to pop_back
, the roles of front
and back
reversed. For example:
front: [] front: [1;2;3;4;5;6]
back: [5;4;3;2;1] back: []
⇓ pop_front ⇓ pop_back
front: [2;3] front: [1;2;3]
back: [5;4] back: [5;4]
In the second half of the course, we'll make use of maps and sets, which are implemented in OCaml using module functors. In the case of maps, we create a module for the type of we want to use for keys. For example, we use Map.Make
to create structures for maps with a given type of key, e.g., StringMap
and IntMap
for maps with string
keys and int
keys, respectively. In the case of sets, we use Set.Make
to create structures for sets with a given type of element.
Implement the function flip_keys_and_values
so that flip_keys_and_values m
is the map m'
gotten by making the value of m
into keys. The value associated with the key v
in m'
is the set of keys in m
which map to the value v
.
This function will require looking in the the OCaml standard library (not stdlib320
) on Maps and Sets.
Not all judgments are derivable, but it's possible to write partial derivations of underivable judgments. These partial derivations can give us insight into why we get certain type errors when type-checking.
Formally, a typing derivation is partial if its leaves are not necessarily axioms. A partial derivation is maximal if every leaf of the derivation is either an axiom or a judgment which cannot be derived by any rule in our specification from any judgments.
Give maximal partial derivations of the following typing judgments.
\varnothing \vdash \texttt{if true then 1 + 3 else x / false} : \texttt{int}
\varnothing \vdash \texttt{if true then 1 + 3 else false / x} : \texttt{int}
In addition, write down the type error which OCaml gives you when trying to type-check each expression. What does the type-error tell you about the order in which OCaml type-checks sub-expressions?
So far, we haven't introduced any rules for typing recursive functions. Here's such a rule.
\frac {\Gamma, f : \tau \to \tau_1, x : \tau \vdash e_1 : \tau_1 \qquad \Gamma, f : \tau \to \tau_1 \vdash e_2 : \tau_2 } {\Gamma \vdash \texttt{let rec} \ f \ x \ \texttt{=} \ e_1 \ \texttt{in} \ e_2 : \tau_2}
Note that this rule puts f
into the context when determining the type of e_1
, the body of f
, which means that f
can appear in its own body (and, hence, may be recursive). Using this rule, give a derivation of the following typing judgment.
\varnothing \vdash \texttt{let rec sum l = match l with | [] -> 0 | h :: t -> h + sum t in sum} : \texttt{int → int}
This will be a somewhat wide derivation. You may use arrows as we've done in lecture to make the derivation less wide, but please make sure your solution is very clear.
\texttt{match 1 :: 2 :: [] with | [] -> true | h :: t -> false} \Downarrow \bot
Give a derivation of the above semantic judgment.