Assignment 3
Submission deadline has been extended to Monday Nov 19 to account for schedule slippage.
Submission Instructions
This assignment is due at 5pm on the date indicated in the
class calendar.
The starter code for the assignment
can be found
here (only for problem 2).
In order to submit your code, pack your
Assignment3.js, default.html
and
part2.sq, part1.sq
files into a tar file
and submit through
this form (same as last time).
The form asks for the URL from where you can download your code.
We will be downloading from all the URLs people submit shortly after 5pm on the due date.
Collaboration: You are allowed to collaborate with one other person for this assignment, but each of you is responsible for submitting your own solution. If you do collaborate with someone else, make sure to name your collaborator in your submission.
If you have any questions, please ask!
Problem 1 (35 pts)
Your goal is to use Synquid to synthesize some
non-trivial functions. We suggest that you first
study the tutorial examples in the
interactive Synquid demo.
Your goal for this exercise is to develop
a function that can serialize a binary search tree into a sorted list.
Below are the definitions of the two main datastructures.
The code block also includes some useful measures that
will be helpful for the transformation.
data BST a where
Empty :: BST a
Node :: x: a -> l: BST {a | _v < x} -> r: BST {a | x < _v} -> BST a
data SortedList a where
Nil :: SortedList a
Cons:: x: a -> xs:SortedList { a | _v < x } -> SortedList a
termination measure height :: BST a -> {Int | _v >= 0} where
Empty -> 0
Node x l r -> 1 + if (height l) < (height r) then (height r) else (height l)
measure keys :: BST a -> Set a where
Empty -> []
Node x l r -> keys l + keys r + [x]
measure vals :: SortedList a -> Set a where
Nil -> []
Cons x xs -> vals xs + [x]
Part a)
Your goal for this part of the assignment is to synthesize a
function
transform: BST a -> SortedList a
that
takes in a
BST
defined as above, and outputs
a
SortedList
containing the exact same values.
You need to set up the synthesis problem so that
Synquid synthesizes the transform function with the desired property.
For this part, you are allowed to use a component
merge
that takes as input two sorted lists and produces
a merged sorted list that contains all the elements in the two input lists.
You do not have to synthesize merge, you are only providing its interface.
Part b)
For this part of the assignment, your goal is to synthesize
a version of
transform
that
does not use
the
merge
function from earlier.
Instead, you will have to synthesize a helper function
helper:: BST a -> SortedList a -> SortedList a
.
The high-level intuition is that the helper function takes as
the second input a partially constructed list
SortedList a
and produces its output by appending all the elements in the BST
to the partially constructed list. Note that this imposes
some strong constraints on the types of
helper
.
For example, since all the elements in the BST will be appended
to the SortedList, it is important that all the elements in the
SortedList be less than all the elements in the BST.
Hints:
Part b) is significantly harder than part a), but here are a few hints
that will help you solve this.
- Expressing the property that all the elements in the temporary list passed to helper
are smaller than all the elements in the BST is tricky. In order to do this, you will
have to define a measure that you can use to express the property that the elements in
the SortedList are all bounded by the BST.
-
The syntax of measures is a bit quirky. For example, note from the examples that the
syntax for a measure implicitly assumes that it is defined over an unnamed recursive datatype.
measure vals :: SortedList a -> Set a where
Nil -> []
Cons x xs -> vals xs + [x]
It is possible to define measures with more than one parameter, but the last parameter
must be the recursive datatype over which the measure is defined and be unnamed, for example:
measure something :: x:a -> SortedList a -> Set a where
Cases for the SortedList that may use parameter x.
-
Reasoning about these measures is tricky, and the solver will need a bit of help.
For example, one thing that the type checker is going to have a
hard time figuring out on its own is that all the elements in the
left subtree are bounded by all the elements on the right subtree.
This can be done using the measure mentioned in the previous hint, but that's going
to require some slight changes to the type signature for the BST.
-
Another fact that the type checker will not be able to discover on its own is that
if all elements in a BST are greater than x, then x is a lower bound for the BST.
In general, if there is a fact about a value that I don't think the type checker
will discover on its own, I can create a function that enforces that fact.
For example, if at some point I want the solver to use the fact that
a > b and b > c implies a > c
,
I can write a function
lemma :: a:Int -> b:{Int | a > _v} -> c:{Int | b > _v} -> \{Int | _v =a && _v > c \}
The synthesizer can easily come up with an implementation for this function,
but more importantly, by calling (lemma a b c)
, the synthesizer
knows it can get a value equal to a
, with the property that
it is greater than both b
and c
without having to
reason about why that is true (that reasoning happened when synthesizing a body
of lemma
). For simple arithmetic reasoning like this, it is never really necessary to add
a lemma like this, but it will be necessary for the more complex property mentioned
earlier.
-
Once you have all the elements in place, it takes about a minute to synthesize
everything on the web interface.
You should submit your answers to this problem in two separate
files named
part1.sq
and
part2.sq
.
Problem 2 (65 pts)
This problem is about quantitative synthesis. Your goal for this
assignment is to extend the simple enumerative synthesizer from
Assignment 1 with a probabilistic grammar for the expression language.
Recall that the grammar from that original assignment was the grammar
shown below.
expr:= num(int n).....................// numeric constant num:int
| flse()...........................// constant FALSE. Unfortunately ,we can't call it false
| vr(string varname)...............// variable var:int
| plus(expr left, expr right)......// arithmetic expressions plus and times
| times(expr left, expr right).....// plus(int,int):int times(int, int):int
| lt(expr left, expr right)........//less than operator lt(int, int):int
| and(expr left, expr right).......// boolean operators and(bool,bool):bool
| not(expr left)...................// not(bool):bool
| ite(expr cond, expr tcase, expr fcase)...// if-then-else; ite(bool, int, int):int
Your goal will be to write a synthesis procedure that takes as a parameter
a probability function
p(t1, pos, t2)
, which will provide
the probability that a node of type
t1
is a child in the position
pos
of a node of type
t2
. So for example,
p(vr, 0, plus)
is the probability that a
plus
node has a
vr
node as its first parameter (in the zero position).
You can assume that nodes that don't type-check would have probability
zero, so for example
p(plus, 0, ite)=0.0
.
Also, $\forall i,m \sum_n p(n, i, m) = 1.0$. In other words, for any
parent node
m
, for any position
i
that is valid
for that parent, the sum of the probabilities of all child nodes
n
must add to $1.0$.
Your goal is to modify the basic bottom-up enumeration algorithm to
ensure that it returns the correct expression with the highest probability.