Essentials of Programming Languages Exercises
Codes
Code for the exercises can be found here.
Exercises
Exercise 0.1 [β ] We often use phrases like βsome languages have property X.β For each such phrase, find one or more languages that have the property and one or more languages that do not have the property. Feel free to ferret out this information from any descriptive book on programming languages (say Scott (2005), Sebesta (2007), or Pratt & Zelkowitz (2001)).
Skipped for now.
Exercise 1.1 [β ] Write inductive definitions of the following sets. Write each definition in all three styles (topdown, bottomup, and rules of inference). Using your rules, show the derivation of some sample elements of each set.
 Do not mention squaring in your rules. As a hint, remember the equation .


Topdown:
if
 , or

Bottomup:
is the smallest set that satisfying the following two properties:
 , and
 If , then

Rules of inference:



Topdown:
if
 , or
 , or

Bottomup:
is the smallest set that satisfying the following two properties:
 , and
 If , then , and
 If , then

Rules of inference:



Topdown:
if
 and , or

Bottomup:
is the smallest set that satisfying the following two properties:
 , and
 If , then

Rules of inference:



Topdown:
if
 and , or

Bottomup:
is the smallest set that satisfying the following two properties:
 , and
 If , then

Rules of inference:

Exercise 1.2 [β β ] What sets are defined by the following pairs of rules? Explain why.
 [β β β ]
Exercise 1.3 [β β β ] Find a set of natural numbers such that , and whenever , then , but , where is the set defined in definition 1.1.2.
Let .
Exercise 1.4 [β ] Write a derivation from ListofInt to
(7 . (3 . (14 . ())))
.
ListofInt
β (
Int .
ListofInt)
β (7 .
ListofInt)
β (7 . (
Int .
ListofInt))
β (7 . (3 .
ListofInt))
β (7 . (3 . (
Int .
ListofInt)))
β (7 . (3 . (14 .
ListofInt)))
β (7 . (3 . (14 . ())))
Exercise 1.5 [β β ] Prove that if e β LcExp, then there are the same number of left and right parentheses in e.
By induction on the structre of LcExp.
If e is of Identifier form, it has left parenthesis and right parenthesis, the hypothesis holds.
If e is of (lambda (
Identifier)
LcExp)
form, the Identifier has parenthese. By induction, LcExp
has the same number of left and right parentheses. Let the number be , then e has left parentheses
and right parentheses. The hypothesis holds.
If e is of (
LcExp
LcExp)
form, let be the number of left or right parentheses in the first LcExp,
let be the number of left or right parentheses in the second LcExp, then e has left
parentheses and right parentheses. The hypothesis holds.
Exercise 1.6 [β ] If we reversed the order of the tests in
nthelement
, what would go wrong?
car
may be applied to empty list.
Exercise 1.7 [β β ] The error message from
nthelement
is uninformative. Rewritenthelement
so that it produces a more informative error message, such as β(a b c)
does not have 8 elements.β
(define reportlisttooshort
(lambda (lst n)
(eopl:error 'nthelement
"~s does not have ~s elements.~%" lst (+ n 1))))
(define nthelementhelper
(lambda (lst n currentlist i)
(if (null? currentlist)
(reportlisttooshort lst n)
(if (zero? i)
(car currentlist)
(nthelementhelper lst n (cdr currentlist) ( i 1))))))
(define nthelement
(lambda (lst n)
(nthelementhelper lst n lst n)))
Exercise 1.8 [β ] In the definition of removefirst, if the last line were replaced by
(removefirst s (cdr los))
, what function would the resulting procedure compute? Give the contract, including the usage statement, for the revised procedure.
removefirst : Sym Γ Listof(Sym) β Listof(Sym)
usage: (removefirst
s
los)
returns a sub of list from los, starting from the symbol after the first
s. If los doesnβt contain s, an empty list is returned.
(define removefirst
(lambda (s los)
(if (null? los)
'()
(if (eqv? (car los) s)
(cdr los)
(removefirst s (cdr los))))))
Exercise 1.9 [β β ] Define
remove
, which is likeremovefirst
, except that it removes all occurrences of a given symbol from a list of symbols, not just the first.
(define remove
(lambda (s los)
(if (null? los)
'()
(if (eqv? (car los) s)
(remove s (cdr los))
(cons (car los) (remove s (cdr los)))))))
Exercise 1.10 [β ] We typically use βorβ to mean βinclusive orβ. What other meanings can βorβ have?
Exclusive or.
Exercise 1.11 [β ] In the last line of
substinsexp
, the recursion is on sexp and not a smaller substructure. Why is the recursion guaranteed to halt?
Because subst
recurs on smaller substructure. We can replace the call to substinsexp
with the body of
substinsexp
, then subst
becomes a normal recursive on a smaller substructure.
Exercise 1.12 [β ] Eliminate the one call to
substinsexp
in subst by replacing it by its definition and simplifying the resulting procedure. The result will be a version of subst that does not needsubstinsexp
. This technique is called inlining, and is used by optimizing compilers.
(define subst
(lambda (new old slist)
(if (null? slist)
'()
(cons (let ([sexp (car slist)])
(if (symbol? sexp)
(if (eqv? sexp old) new sexp)
(subst new old sexp)))
(subst new old (cdr slist))))))
Exercise 1.13 [β β ] In our example, we began by eliminating the Kleene star in the grammar for Slist. Write
subst
following the original grammar by usingmap
.
(define substinsexp
(lambda (new old sexp)
(if (symbol? sexp)
(if (eqv? sexp old) new sexp)
(subst new old sexp))))
(define subst
(lambda (new old slist)
(map (lambda (sexp) (substinsexp new old sexp))
slist)))
Exercise 1.14 [β β ] Given the assumption 0 β€ n < length(v), prove that
partialvectorsum
is correct.
Since 0 β€ n < length(v), we know that length(v) is at leat 1, so that v contains at least one element. We
prove partialvectorsum
is correct by induction over n.
Base case: if n equals to 0, (partialvectorsum
v
n)
equals to (vectorref
v 0)
, which equals to
, the claim holds.
Inductive case: if n β 0, n (partialvectorsum
v
n)
equals to
(add (vectorref
v
n) (partialvectorsum
v (
n 1)))
, which equals to
, which equals to , the claim holds.
Exercise 1.15 [β ]
(duple n x)
returns a list containingn
copies ofx
.> (duple 2 3) (3 3) > (duple 4 '(ha ha)) ((ha ha) (ha ha) (ha ha) (ha ha)) > (duple 0 '(blah)) ()
(define duplehelper
(lambda (lst n x)
(if (zero? n)
lst
(duplehelper (cons x lst) ( n 1) x))))
(define duple
(lambda (n x)
(duplehelper '() n x)))
Exercise 1.16 [β ]
(invert lst)
, where lst is a list of 2lists (lists of length two), returns a list with each 2list reversed.> (invert '((a 1) (a 2) (1 b) (2 b))) ((1 a) (2 a) (b 1) (b 2))
(define invert
(lambda (lst)
(map (lambda (x) (list (cadr x) (car x)))
lst)))
Exercise 1.17 [β ]
(down lst)
wraps parentheses around each toplevel element oflst
.> (down '(1 2 3)) ((1) (2) (3)) > (down '((a) (fine) (idea))) (((a)) ((fine)) ((idea))) > (down '(a (more (complicated)) object)) ((a) ((more (complicated))) (object))
(define down
(lambda (lst)
(map (lambda (x) (list x))
lst)))
Exercise 1.18 [β ]
(swapper s1 s2 slist)
returns a list the same as slist, but with all occurrences ofs1
replaced bys2
and all occurrences ofs2
replaced bys1
.> (swapper 'a 'd '(a b c d)) (d b c a) > (swapper 'a 'd '(a d () c d)) (d a () c a) > (swapper 'x 'y '((x) y (z (x)))) ((y) x (z (y)))
(define swapper
(lambda (s1 s2 slist)
(map (lambda (sexp)
(if (symbol? sexp)
(if (eqv? sexp s1)
s2
(if (eqv? sexp s2)
s1
sexp))
(swapper s1 s2 sexp)))
slist)))
Exercise 1.19 [β ]
(listset lst n x)
returns a list likelst
, except that then
th element, using zerobased indexing, isx
.> (listset '(a b c d) 2 '(1 2)) (a b (1 2) d) > (listref (listset '(a b c d) 3 '(1 5 10)) 3) (1 5 10)
(define listset
(lambda (lst n x)
(if (zero? n)
(cons x (cdr lst))
(cons (car lst) (listset (cdr lst) ( n 1) x)))))
Exercise 1.20 [β ]
(countoccurrences s slist)
returns the number of occurrences ofs
inslist
.> (countoccurrences 'x '((f x) y (((x z) x)))) 3 > (countoccurrences 'x '((f x) y (((x z) () x)))) 3 > (countoccurrences 'w '((f x) y (((x z) x)))) 0
(define countoccurrencessexp
(lambda (s sexp)
(if (symbol? sexp)
(if (eqv? sexp s) 1 0)
(countoccurrences s sexp))))
(define countoccurrences
(lambda (s slist)
(if (null? slist)
0
(+ (countoccurrencessexp s (car slist))
(countoccurrences s (cdr slist))))))
Exercise 1.21 [β β ]
(product sos1 sos2)
, wheresos1
andsos2
are each a list of symbols without repetitions, returns a list of 2lists that represents the Cartesian product ofsos1
andsos2
. The 2lists may appear in any order.> (product '(a b c) '(x y)) ((a x) (a y) (b x) (b y) (c x) (c y))
(define productsymbol
(lambda (tail s sos)
(if (null? sos)
tail
(productsymbol (cons (list s (car sos)) tail) s (cdr sos)))))
(define producthelper
(lambda (tail sos1 sos2)
(if (null? sos1)
tail
(producthelper (productsymbol tail (car sos1) sos2)
(cdr sos1)
sos2))))
(define product
(lambda (sos1 sos2)
(producthelper '() sos1 sos2)))
Exercise 1.22 [β β ]
(filterin pred lst)
returns the list of those elements inlst
that satisfy the predicatepred
.> (filterin number? '(a 2 (1 3) b 7)) (2 7) > (filterin symbol? '(a (b c) 17 foo)) (a foo)
(define filterin
(lambda (pred lst)
(if (null? lst)
'()
(let ([element (car lst)]
[tail (filterin pred (cdr lst))])
(if (pred element)
(cons element tail)
tail)))))
Exercise 1.23 [β β ]
(listindex pred lst)
returns the 0based position of the first element oflst
that satisfies the predicatepred
. If no element oflst
satisfies the predicate, thenlistindex
returns#f
.> (listindex number? '(a 2 (1 3) b 7)) 1 > (listindex symbol? '(a (b c) 17 foo)) 0 > (listindex symbol? '(1 2 (a b) 3)) #f
(define listindexhelper
(lambda (n pred lst)
(if (null? lst)
#f
(if (pred (car lst))
n
(listindexhelper (+ n 1) pred (cdr lst))))))
(define listindex
(lambda (pred lst)
(listindexhelper 0 pred lst)))
Exercise 1.24 [β β ]
(every? pred lst)
returns#f
if any element oflst
fails to satisfypred
, and returns#t
otherwise.> (every? number? '(a b c 3 e)) #f > (every? number? '(1 2 3 5 4)) #t
(define every?
(lambda (pred lst)
(if (null? lst)
#t
(and (pred (car lst))
(every? pred (cdr lst))))))
Exercise 1.25 [β β ]
(exists? pred lst)
returns#t
if any element oflst
satisfiespred
, and returns#f
otherwise.> (exists? number? '(a b c 3 e)) #t > (exists? number? '(a b c d e)) #f
(define exists?
(lambda (pred lst)
(if (null? lst)
#f
(or (pred (car lst))
(exists? pred (cdr lst))))))
Exercise 1.26 [β β ]
(up lst)
removes a pair of parentheses from each toplevel element oflst
. If a toplevel element is not a list, it is included in the result, as is. The value of(up (down lst))
is equivalent to lst, but(down (up lst))
is not necessarilylst
. (See exercise 1.17.)> (up '((1 2) (3 4))) (1 2 3 4) > (up '((x (y)) z)) (x (y) z)
(define extendhead
(lambda (tail head)
(if (null? head)
tail
(cons (car head) (extendhead tail (cdr head))))))
(define upelement
(lambda (tail element)
(if (list? element)
(extendhead tail element)
(cons element tail))))
(define up
(lambda (lst)
(if (null? lst)
'()
(upelement (up (cdr lst)) (car lst)))))
Exercise 1.27 [β β ]
(flatten slist)
returns a list of the symbols contained inslist
in the order in which they occur whenslist
is printed. Intuitively,flatten
removes all the inner parentheses from its argument.> (flatten '(a b c)) (a b c) > (flatten '((a) () (b ()) () (c))) (a b c) > (flatten '((a b) c (((d)) e))) (a b c d e) > (flatten '(a b (() (c)))) (a b c)
(define flattenelement
(lambda (tail element)
(if (list? element)
(flattenhelper tail element)
(cons element tail))))
(define flattenhelper
(lambda (tail slist)
(if (null? slist)
tail
(flattenelement (flattenhelper tail (cdr slist))
(car slist)))))
(define flatten
(lambda (slist)
(flattenhelper '() slist)))
Exercise 1.28 [β β ]
(merge loi1 loi2)
, whereloi1
andloi2
are lists of integers that are sorted in ascending order, returns a sorted list of all the integers inloi1
andloi2
.> (merge '(1 4) '(1 2 8)) (1 1 2 4 8) > (merge '(35 62 81 90 91) '(3 83 85 90)) (3 35 62 81 83 85 90 90 91)
(define mergehelper
(lambda (loi1 loi2)
(if (null? loi1)
loi2
(let ([i1 (car loi1)]
[i2 (car loi2)])
(if (< i1 i2)
(cons i1 (mergehelper (cdr loi1) loi2))
(cons i2 (mergehelper (cdr loi2) loi1)))))))
(define merge
(lambda (loi1 loi2)
(if (null? loi1)
loi2
(mergehelper loi2 loi1))))
Exercise 1.29 [β β ]
(sort loi)
returns a list of the elements ofloi
in ascending order.> (sort '(8 2 5 2 3)) (2 2 3 5 8)
(define getrun
(lambda (loi)
(let ([head1 (car loi)]
[tail1 (cdr loi)])
(if (null? tail1)
(cons loi '())
(let ([head2 (car tail1)])
(if (<= head1 head2)
(let ([tailrun (getrun tail1)])
(cons (cons head1 (car tailrun)) (cdr tailrun)))
(cons (list head1) tail1)))))))
(define merge
(lambda (run1 run2)
(let ([head1 (car run1)]
[head2 (car run2)])
(if (<= head1 head2)
(let ([tail1 (cdr run1)])
(if (null? tail1)
(cons head1 run2)
(cons head1 (merge tail1 run2))))
(let ([tail2 (cdr run2)])
(if (null? tail2)
(cons head2 run1)
(cons head2 (merge run1 tail2))))))))
(define collapseall
(lambda (stack run)
(if (null? stack)
run
(collapseall (cdr stack) (merge (cdar stack) run)))))
(define collapse
(lambda (stack level run)
(if (null? stack)
(list (cons level run))
(let ([top (car stack)])
(if (= (car top) level)
(collapse (cdr stack) (+ level 1) (merge (cdr top) run))
(cons (cons level run) stack))))))
(define sorthelper
(lambda (stack loi)
(let* ([runandtail (getrun loi)]
[run (car runandtail)]
[tail (cdr runandtail)])
(if (null? tail)
(collapseall stack run)
(sorthelper (collapse stack 0 run) tail)))))
(define sort
(lambda (loi)
(if (null? loi)
'()
(sorthelper '() loi))))
Exercise 1.30 [β β ]
(sort/predicate pred loi)
returns a list of elements sorted by the predicate.> (sort/predicate < '(8 2 5 2 3)) (2 2 3 5 8) > (sort/predicate > '(8 2 5 2 3)) (8 5 3 2 2)
(define getrun
(lambda (pred loi)
(let ([head1 (car loi)]
[tail1 (cdr loi)])
(if (null? tail1)
(cons loi '())
(let ([head2 (car tail1)])
(if (pred head2 head1)
(cons (list head1) tail1)
(let ([tailrun (getrun pred tail1)])
(cons (cons head1 (car tailrun)) (cdr tailrun)))))))))
(define merge
(lambda (pred run1 run2)
(let ([head1 (car run1)]
[head2 (car run2)])
(if (pred head2 head1)
(let ([tail2 (cdr run2)])
(if (null? tail2)
(cons head2 run1)
(cons head2 (merge pred run1 tail2))))
(let ([tail1 (cdr run1)])
(if (null? tail1)
(cons head1 run2)
(cons head1 (merge pred tail1 run2))))))))
(define collapseall
(lambda (pred stack run)
(if (null? stack)
run
(collapseall pred (cdr stack) (merge pred (cdar stack) run)))))
(define collapse
(lambda (pred stack level run)
(if (null? stack)
(list (cons level run))
(let ([top (car stack)])
(if (= (car top) level)
(collapse pred (cdr stack) (+ level 1) (merge pred (cdr top) run))
(cons (cons level run) stack))))))
(define sorthelper
(lambda (pred stack loi)
(let* ([runandtail (getrun pred loi)]
[run (car runandtail)]
[tail (cdr runandtail)])
(if (null? tail)
(collapseall pred stack run)
(sorthelper pred (collapse pred stack 0 run) tail)))))
(define sort/predicate
(lambda (pred loi)
(if (null? loi)
'()
(sorthelper pred '() loi))))
Exercise 1.31 [β ] Write the following procedures for calculating on a bintree (definition 1.1.7):
leaf
andinteriornode
, which build bintrees,leaf?
, which tests whether a bintree is a leaf, andlson
,rson
, andcontentsof
, which extract the components of a node.contentsof
should work on both leaves and interior nodes.
(define leaf
(lambda (num)
num))
(define interiornode
(lambda (symbol leftchild rightchild)
(cons symbol (cons leftchild rightchild))))
(define leaf? integer?)
(define lson cadr)
(define rson cddr)
(define contentsof
(lambda (bintree)
(if (leaf? bintree)
bintree
(car bintree))))
Exercise 1.32 [β ] Write a procedure
doubletree
that takes a bintree, as represented in definition 1.1.7, and produces another bintree like the original, but with all the integers in the leaves doubled.
(define doubletree
(lambda (bintree)
(if (leaf? bintree)
(leaf (* (contentsof bintree) 2))
(interiornode (contentsof bintree)
(doubletree (lson bintree))
(doubletree (rson bintree))))))
Exercise 1.33 [β β ] Write a procedure
markleaveswithreddepth
that takes a bintree (definition 1.1.7), and produces a bintree of the same shape as the original, except that in the new tree, each leaf contains the number of nodes between it and the root that contain the symbolred
. For example, the expression(markleaveswithreddepth (interiornode 'red (interiornode 'bar (leaf 26) (leaf 12)) (interiornode 'red (leaf 11) (interiornode 'quux (leaf 117) (leaf 14)))))
which is written using the procedures defined in exercise 1.31, should return the bintree
(red (bar 1 1) (red 2 (quux 2 2)))
(define markleaveswithreddepthhelper
(lambda (bintree rednum)
(if (leaf? bintree)
(leaf rednum)
(let* ([content (contentsof bintree)]
[newrednum (if (eqv? content 'red) (+ rednum 1) rednum)])
(interiornode content
(markleaveswithreddepthhelper (lson bintree) newrednum)
(markleaveswithreddepthhelper (rson bintree) newrednum))))))
(define markleaveswithreddepth
(lambda (bintree)
(markleaveswithreddepthhelper bintree 0)))
Exercise 1.34 [β β β ] Write a procedure
path
that takes an integern
and a binary search treebst
(page 10) that contains the integern
, and returns a list ofleft
s andright
s showing how to find the node containingn
. Ifn
is found at the root, it returns the empty list.> (path 17 '(14 (7 () (12 () ())) (26 (20 (17 () ()) ()) (31 () ())))) (right left left)
(define path
(lambda (n bst)
(let ([head (car bst)])
(if (< n head)
(cons 'left (path n (cadr bst)))
(if (= n head)
'()
(cons 'right (path n (caddr bst))))))))
Exercise 1.35 [β β β ] Write a procedure
numberleaves
that takes a bintree, and produces a bintree like the original, except the contents of the leaves are numbered starting from 0. For example,(numberleaves (interiornode 'foo (interiornode 'bar (leaf 26) (leaf 12)) (interiornode 'baz (leaf 11) (interiornode 'quux (leaf 117) (leaf 14)))))
should return
(foo (bar 0 1) (baz 2 (quux 3 4)))
(define numberleaveshelper
(lambda (bintree n)
(if (leaf? bintree)
(cons (leaf n) (+ n 1))
(let* ([leftresult (numberleaveshelper (lson bintree) n)]
[rightresult (numberleaveshelper (rson bintree) (cdr leftresult))])
(cons (interiornode (contentsof bintree)
(car leftresult)
(car rightresult))
(cdr rightresult))))))
(define numberleaves
(lambda (bintree)
(car (numberleaveshelper bintree 0))))
Exercise 1.36 [β β β ] Write a procedure
g
such thatnumberelements
from page 23 could be defined as(define numberelements (lambda (lst) (if (null? lst) '() (g (list 0 (car lst)) (numberelements (cdr lst))))))
(define g
(lambda (head tail)
(cons head
(map (lambda (item)
(list (+ (car item) 1) (cadr item)))
tail))))
Exercise 2.1 [β ] Implement the four required operations for bigits. Then use your implementation to calculate the factorial of 10. How does the execution time vary as this argument changes? How does the execution time vary as the base changes? Explain why.
(define *base* 10)
(define *basesub1* ( *base* 1))
(define zero
(lambda ()
'()))
(define iszero? null?)
(define successor
(lambda (n)
(if (null? n)
'(1)
(let ([lowestdigit (car n)])
(if (= lowestdigit *basesub1*)
(cons 0 (successor (cdr n)))
(cons (+ lowestdigit 1) (cdr n)))))))
(define predecessor
(lambda (n)
(let ([lowestdigit (car n)]
[restdigits (cdr n)])
(if (= lowestdigit 0)
(cons *basesub1* (predecessor restdigits))
(if (and (= lowestdigit 1) (null? restdigits))
'()
(cons ( lowestdigit 1) (cdr n)))))))
(define plus
(lambda (m n)
(if (iszero? n)
m
(plus (successor m) (predecessor n)))))
(define multiplyhelper
(lambda (base m n)
(if (iszero? n)
base
(multiplyhelper (plus base m) m (predecessor n)))))
(define multiply
(lambda (m n)
(multiplyhelper (zero) m n)))
(define factorialhelper
(lambda (base n)
(if (iszero? n)
base
(factorialhelper (multiply base n) (predecessor n)))))
(define factorial
(lambda (n)
(factorialhelper (successor (zero)) n)))
When the argument of factorial
becomes larger, the execution time becomes longer. Obviously.
The execution time becomes shorter when the base becomes larger. I think thatβs because fewer allocations are needed when the base becomes larger.
Exercise 2.2 [β β ] Analyze each of these proposed representations critically. To what extent do they succeed or fail in satisfying the specification of the data type?
 Unary representation. Too much space consumed.
 Scheme number representation. Not every language has native big integer support.
 Bignum representation. Not easy to implement.
Exercise 2.3 [β β ] Define a representation of all the integers (negative and nonnegative) as difftrees, where a difftree is a list defined by the grammar
Difftree ::=
(one)
(diff
DifftreeDifftree
)
The list
(one)
represents 1. If represents and represents , then(diff t1 t2)
is a representation of .So both
(one)
and(diff (one) (diff (one) (one)))
are representations of 1;(diff (diff (one) (one)) (one))
is a representation of 1.
 Show that every number has infinitely many representations in this system.
 Turn this representation of the integers into an implementation by writing
zero
,iszero?
,successor
, andpredecessor
, as specified on page 32, except that now the negative integers are also represented. Your procedures should take as input any of the multiple legal representations of an integer in this scheme. For example, if yoursuccessor
procedure is given any of the infinitely many legal representations of 1, it should produce one of the legal representations of 2. It is permissible for different legal representations of 1 to yield different legal representations of 2. Write a procedure
difftreeplus
that does addition in this representation. Your procedure should be optimized for the difftree representation, and should do its work in a constant amount of time (independent of the size of its inputs). In particular, it should not be recursive.
 0 has infinitely many representations:
(diff (one) (one))
,(diff (diff (one) (one)) (diff (one) (one)))
, and so on. n can be represented as(diff
n)
, since 0 has infinitely many representations, n has infinitely many representations. 
(define zero (lambda () '(diff (one) (one)))) (define interpret (lambda (n) (if (eqv? (car n) 'one) 1 ( (interpret (cadr n)) (interpret (caddr n)))))) (define iszero? (lambda (n) (zero? (interpret n)))) (define successor (lambda (n) (list 'diff n '(diff (diff (one) (one)) (one))))) (define predecessor (lambda (n) (list 'diff n '(one))))

(define difftreeplus (lambda (m n) (list 'diff m (list 'diff '(diff (one) (one)) n))))
Exercise 2.4 [β β ] Consider the data type of stacks of values, with an interface consisting of the procedures
emptystack
,push
,pop
,top
, andemptystack?
. Write a specification for these operations in the style of the example above. Which operations are constructors and which are observers?
(emptystack)
= ββ β(push
βfβ)
= βgβ, where g(0) = v, and g(n + 1) = f(n)(pop
βfβ)
= g, where g(n) = f(n + 1)(top
βfβ)
= βf(0)β(emptystack?
βfβ)
=#t
if f = β ,#f
otherwise
Constructors: emptystack
, push
and pop
.
Observers: top
and emptystack?
.
Exercise 2.5 [β ] We can use any data structure for representing environments, if we can distinguish empty environments from nonempty ones, and in which one can extract the pieces of a nonempty environment. Implement environments using a representation in which the empty environment is represented as the empty list, and in which
extendenv
builds an environment that looks likeβββββ¬ββββ β β· β βΆββΌββΊ savedenv βββΌββ΄ββββ βΌ βββββ¬ββββ β β· β β· β βββΌββ΄ββΌββ βββββ βββββ βΌ βΌ savedvar savedval
This is called an alist or associationlist representation.
(define emptyenv
(lambda ()
'()))
(define applyenv
(lambda (env searchvar)
(let ([head (car env)])
(if (eqv? (car head) searchvar)
(cdr head)
(applyenv (cdr env) searchvar)))))
(define extendenv
(lambda (var val env)
(cons (cons var val) env)))
Exercise 2.6 [β ] Invent at least three different representations of the environment interface and implement them.
Deferred.
Exercise 2.7 [β ] Rewrite
applyenv
in figure 2.1 to give a more informative error message.
(define emptyenv
(lambda ()
'(emptyenv)))
(define extendenv
(lambda (var val env)
(list 'extendenv var val env)))
(define applyenv
(lambda (env searchvar)
(let loop ([env1 env])
(cond
[(eqv? (car env1) 'emptyenv) (reportnobindingfound searchvar env)]
[(eqv? (car env1) 'extendenv) (let ([savedvar (cadr env1)]
[savedval (caddr env1)]
[savedenv (cadddr env1)])
(if (eqv? searchvar savedvar)
savedval
(loop savedenv)))]
[else (reportinvalidenv env1)]))))
(define collectbindings
(lambda (env)
(let loop ([base '()]
[env env])
(let ([tag (car env)])
(cond [(eqv? tag 'emptyenv) base]
[(eqv? tag 'extendenv) (let ([savedvar (cadr env)]
[savedval (caddr env)]
[savedenv (cadddr env)])
(loop (if (assv savedvar base)
base
(cons (cons savedvar savedval) base))
savedenv))])))))
(define reportnobindingfound
(lambda (searchvar env)
(eopl:error 'applyenv "No binding for ~s. All bindings: ~s" searchvar (collectbindings env))))
(define reportinvalidenv
(lambda (env)
(eopl:error 'applyenv "Bad environment: ~s" env)))
Exercise 2.8 [β ] Add to the environment interface an observer called
emptyenv?
and implement it using the alist representation.
(emtpyenv?
βfβ)
= #t
if f = β
, #f
otherwise.
(define emptyenv? null?)
Exercise 2.9 [β ] Add to the environment interface an observer called
hasbinding?
that takes an environment env and a variable s and tests to see if s has an associated value in env. Implement it using the alist representation.
(hasbinding?
βfβ)
= #t
if f(var) = val for some var and val, #f
otherwise.
(define hasbinding?
(lambda (env searchvar)
(cond [(null? env) #f]
[(eqv? (caar env) searchvar) #t]
[else (hasbinding? (cdr env) searchvar)])))
Exercise 2.10 [β ] Add to the environment interface a constructor
extendenv*
, and implement it using the alist representation. This constructor takes a list of variables, a list of values of the same length, and an environment, and is specified by
(extendenv* (
var_{1} β¦ var_{k}) (
val_{1} β¦ val_{k})
βfβ)
= βgβ, where g(var) = val_{i} if var = var_{i} for some i such that 1 β€ i β€ k, f(var) otherwise.
(define extendenv*
(lambda (vars vals env)
(if (null? vars)
env
(extendenv* (cdr vars)
(cdr vals)
(cons (cons (car vars) (car vals)) env)))))
Exercise 2.11 [β β ] A naive implementation of
extendenv*
from the preceding exercise requires time proportional to k to run. It is possible to represent environments so thatextendenv*
requires only constant time: represent the empty environment by the empty list, and represent a nonempty environment by the data structureβββββ¬ββββ β β· β βΆββΌββΊ savedenv βββΌββ΄ββββ βΌ βββββ¬ββββ β β· β β· β βββΌββ΄ββΌββ βββββ βββββ βΌ βΌ savedvars savedvals
Such an environment might look like
backbone β βββββ¬ββββ βΌ βββββ¬ββββ βββββ¬ββββ β β· β βΆββΌβββββββββββΊβ β· β βΆββΌβββββββββββΊβ β· β βΆββΌβββββββββββΊ rest of environment βββΌββ΄ββββ βββΌββ΄ββββ βββΌββ΄ββββ βΌ βΌ βΌ βββββ¬ββββ βββββ¬ββββ βββββ¬ββββ β β· β β· β β β· β β· β β β· β β· β βββΌββ΄ββΌββ βββΌββ΄ββΌββ βββΌββ΄ββΌββ ββββ ββββ ββββ ββββ ββββ ββββ βΌ βΌ βΌ βΌ βΌ βΌ (a b c) (11 12 13) (x z) (66 77) (x y) (88 99)
This is called the ribcage representation. The environment is represented as a list of pairs called ribs; each left rib is a list of variables and each right rib is the corresponding list of values.
Implement the environment interface, including
extendenv*
, in this representation.
(define emptyenv
(lambda ()
'()))
(define applyenv
(lambda (env searchvar)
(let loop ([env env])
(let ([rib (car env)])
(let applyrib ([vars (car rib)]
[vals (cdr rib)])
(cond [(null? vars) (loop (cdr env))]
[(eqv? (car vars) searchvar) (car vals)]
[else (applyrib (cdr vars) (cdr vals))]))))))
(define extendenv*
(lambda (vars vals env)
(cons (cons vars vals) env)))
(define extendenv
(lambda (var val env)
(extendenv* (list var) (list val) env)))
Exercise 2.12 [β ] Implement the stack data type of exercise 2.4 using a procedural representation.
(define emptystack
(lambda ()
(lambda (command)
(cond [(eqv? command 'empty?) #t]))))
(define push
(lambda (stack val)
(lambda (command)
(cond [(eqv? command 'empty?) #f]
[(eqv? command 'pop) stack]
[(eqv? command 'top) val]))))
(define pop
(lambda (stack)
(stack 'pop)))
(define top
(lambda (stack)
(stack 'top)))
(define emptystack?
(lambda (stack)
(stack 'empty?)))
Exercise 2.13 [β β ] Extend the procedural representation to implement
emptyenv?
by representing the environment by a list of two procedures: one that returns the value associated with a variable, as before, and one that returns whether or not the environment is empty.
(define reportnobindingfound
(lambda (searchvar)
(eopl:error 'applyenv "No binding for ~s" searchvar)))
(define emptyenv
(lambda ()
(list (lambda (searchvar)
(reportnobindingfound searchvar))
(lambda ()
#t))))
(define emptyenv?
(lambda (env)
((cadr env))))
(define extendenv
(lambda (savedvar savedval savedenv)
(list (lambda (searchvar)
(if (eqv? searchvar savedvar)
savedval
(applyenv savedenv searchvar)))
(lambda ()
#f))))
(define applyenv
(lambda (env searchvar)
((car env) searchvar)))
Exercise 2.14 [β β ] Extend the representation of the preceding exercise to include a third procedure that implements
hasbinding?
(see exercise 2.9).
(define reportnobindingfound
(lambda (searchvar)
(eopl:error 'applyenv "No binding for ~s" searchvar)))
(define emptyenv
(lambda ()
(list (lambda (searchvar)
(reportnobindingfound searchvar))
(lambda ()
#t)
(lambda (searchvar)
#f))))
(define emptyenv?
(lambda (env)
((cadr env))))
(define extendenv
(lambda (savedvar savedval savedenv)
(list (lambda (searchvar)
(if (eqv? searchvar savedvar)
savedval
(applyenv savedenv searchvar)))
(lambda ()
#f)
(lambda (searchvar)
(or (eqv? savedvar searchvar)
(hasbinding? savedenv searchvar))))))
(define applyenv
(lambda (env searchvar)
((car env) searchvar)))
(define hasbinding?
(lambda (env searchvar)
((caddr env) searchvar)))
Exercise 2.15 [β ] Implement the lambdacalculus expression interface for the representation specified by the grammar above.
(define varexp
(lambda (var)
var))
(define lambdaexp
(lambda (boundvar body)
`(lambda (,boundvar)
,body)))
(define appexp
(lambda (operator operand)
`(,operator ,operand)))
(define varexp? symbol?)
(define lambdaexp?
(lambda (exp)
(and (pair? exp)
(eqv? (car exp) 'lambda))))
(define appexp?
(lambda (exp)
(and (pair? exp)
(not (eqv? (car exp) 'lambda)))))
(define varexp>var
(lambda (exp)
exp))
(define lambdaexp>boundvar caadr)
(define lambdaexp>body caddr)
(define appexp>rator car)
(define appexp>rand cadr)
Exercise 2.16 [β ] Modify the implementation to use a representation in which there are no parentheses around the bound variable in a
lambda
expression.
(define lambdaexp
(lambda (boundvar body)
`(lambda ,boundvar ,body)))
(define lambdaexp>boundvar cadr)
Remaining implementations are the same as the ones in exercise 2.15.
Exercise 2.17 [β ] Invent at least two other representations of the data type of lambdacalculus expressions and implement them.
Skipped for now.
Exercise 2.18 [β ] We usually represent a sequence of values as a list. In this representation, it is easy to move from one element in a sequence to the next, but it is hard to move from one element to the preceding one without the help of context arguments. Implement nonempty bidirectional sequences of integers, as suggested by the grammar
NodeInSequence ::=
(
IntListof
(
Int)
Listof(
Int))
The first list of numbers is the elements of the sequence preceding the current one, in reverse order, and the second list is the elements of the sequence after the current one. For example,
(6 (5 4 3 2 1) (7 8 9))
represents the list(1 2 3 4 5 6 7 8 9)
, with the focus on the element 6.In this representation, implement the procedure
number>sequence
, which takes a number and produces a sequence consisting of exactly that number. Also implementcurrentelement
,movetoleft
,movetoright
,inserttoleft
,inserttoright
,atleftend?
, andatrightend?
.For example:
> (number>sequence 7) (7 () ()) > (currentelement '(6 (5 4 3 2 1) (7 8 9))) 6 > (movetoleft '(6 (5 4 3 2 1) (7 8 9))) (5 (4 3 2 1) (6 7 8 9)) > (movetoright '(6 (5 4 3 2 1) (7 8 9))) (7 (6 5 4 3 2 1) (8 9)) > (inserttoleft 13 '(6 (5 4 3 2 1) (7 8 9))) (6 (13 5 4 3 2 1) (7 8 9)) > (inserttoright 13 '(6 (5 4 3 2 1) (7 8 9))) (6 (5 4 3 2 1) (13 7 8 9))
The procedure
movetoright
should fail if its argument is at the right end of the sequence, and the proceduremovetoleft
should fail if its argument is at the left end of the sequence.
(define number>sequence
(lambda (num)
(list num '() '())))
(define currentelement car)
(define movetoleft
(lambda (node)
(let ([before (cadr node)])
(if (null? before)
(eopl:error 'movetoleft "Cannot move to left when at left end.")
(let ([num (car node)]
[after (caddr node)])
(list (car before) (cdr before) (cons num after)))))))
(define movetoright
(lambda (node)
(let ([after (caddr node)])
(if (null? after)
(eopl:error 'movetoright "Cannot move to right when at right end.")
(let ([num (car node)]
[before (cadr node)])
(list (car after) (cons num before) (cdr after)))))))
(define inserttoleft
(lambda (num node)
(let ([current (car node)]
[before (cadr node)]
[after (caddr node)])
(list current (cons num before) after))))
(define inserttoright
(lambda (num node)
(let ([current (car node)]
[before (cadr node)]
[after (caddr node)])
(list current before (cons num after)))))
(define atleftend?
(lambda (node)
(null? (cadr node))))
(define atrightend?
(lambda (node)
(null? (caddr node))))
Exercise 2.19 [β ] A binary tree with empty leaves and with interior nodes labeled with integers could be represented using the grammar
Bintree ::=
()
(
IntBintree
Bintree
)
In this representation, implement the procedure
number>bintree
, which takes a number and produces a binary tree consisting of a single node containing that number. Also implementcurrentelement
,movetoleftson
,movetorightson
,atleaf?
,inserttoleft
, andinserttoright
. For example,> (number>bintree 13) (13 () ()) > (define t1 (inserttoright 14 (inserttoleft 12 (number>bintree 13)))) > t1 (13 (12 () ()) (14 () ())) > (movetoleftson t1) (12 () ()) > (currentelement (movetoleftson t1)) 12 > (atleaf? (movetorightson (movetoleftson t1))) #t > (inserttoleft 15 t1) (13 (15 (12 () ()) ()) (14 () ()))
(define number>bintree
(lambda (num)
`(,num () ())))
(define currentelement car)
(define movetoleftson cadr)
(define movetorightson caddr)
(define atleaf? null?)
(define inserttoleft
(lambda (num bintree)
(let ([rootvalue (car bintree)]
[leftchild (cadr bintree)]
[rightchild (caddr bintree)])
`(,rootvalue (,num ,leftchild ()) ,rightchild))))
(define inserttoright
(lambda (num bintree)
(let ([rootvalue (car bintree)]
[leftchild (cadr bintree)]
[rightchild (caddr bintree)])
`(,rootvalue ,leftchild (,num () ,rightchild)))))
Exercise 2.20 [β β β ] In the representation of binary trees in exercise 2.19 it is easy to move from a parent node to one of its sons, but it is impossible to move from a son to its parent without the help of context arguments. Extend the representation of lists in exercise 2.18 to represent nodes in a binary tree. As a hint, consider representing the portion of the tree above the current node by a reversed list, as in exercise 2.18.
In this representation, implement the procedures from exercise 2.19. Also implement
moveup
andatroot?
.
(define number>bintree
(lambda (num)
(cons `(,num () ()) '())))
(define currentelement caar)
(define movetoleftson
(lambda (bintree)
(let* ([current (car bintree)]
[value (car current)]
[leftson (cadr current)]
[rightson (caddr current)]
[parents (cdr bintree)])
(cons leftson
(cons (list value 'right rightson)
parents)))))
(define movetorightson
(lambda (bintree)
(let* ([current (car bintree)]
[value (car current)]
[leftson (cadr current)]
[rightson (caddr current)]
[parents (cdr bintree)])
(cons rightson
(cons (list value 'left leftson)
parents)))))
(define atleaf?
(lambda (bintree)
(null? (car bintree))))
(define inserttoleft
(lambda (num bintree)
(let* ([current (car bintree)]
[value (car current)]
[leftson (cadr current)]
[rightson (caddr current)]
[parents (cdr bintree)])
(cons `(,value (,num ,leftson ()) ,rightson)
parents))))
(define inserttoright
(lambda (num bintree)
(let* ([current (car bintree)]
[value (car current)]
[leftson (cadr current)]
[rightson (caddr current)]
[parents (cdr bintree)])
(cons `(,value ,leftson (,num () ,rightson))
parents))))
(define moveup
(lambda (bintree)
(let* ([current (car bintree)]
[parents (cdr bintree)]
[parent (car parents)]
[parentvalue (car parent)]
[parentotherbranch (cadr parent)]
[parentotherson (caddr parent)]
[restparents (cdr parents)])
(if (eqv? parentotherbranch 'left)
(cons (list parentvalue parentotherson current)
restparents)
(cons (list parentvalue current parentotherson)
restparents)))))
(define atroot?
(lambda (bintree)
(null? (cdr bintree))))
Exercise 2.21 [β ] Implement the data type of environments, as in section 2.2.2, using
definedatatype
. Then includehasbinding?
of exercise 2.9.
(definedatatype envtype env?
(emptyenv)
(extendenv [var symbol?]
[val always?]
[env env?]))
(define applyenv
(lambda (env searchvar)
(cases envtype env
[emptyenv () (eopl:error 'applyenv "No binding for ~s" searchvar)]
[extendenv (var val env) (if (eqv? var searchvar)
val
(applyenv env searchvar))])))
(define hasbinding?
(lambda (env searchvar)
(cases envtype env
[emptyenv () #f]
[extendenv (var val env) (or (eqv? var searchvar)
(hasbinding? env searchvar))])))
Exercise 2.22 [β ] Using
definedatatype
, implement the stack data type of exercise 2.4.
(definedatatype stacktype stack?
(emptystack)
(push [savedstack stack?]
[val always?]))
(define pop
(lambda (stack)
(cases stacktype stack
[emptystack () (eopl:error 'pop "Can not pop an empty stack.")]
[push (savedstack val) savedstack])))
(define top
(lambda (stack)
(cases stacktype stack
[emptystack () (eopl:error 'pop "Can not top an empty stack.")]
[push (savedstack val) val])))
(define emptystack?
(lambda (stack)
(cases stacktype stack
[emptystack () #t]
[push (savedstack val) #f])))
Exercise 2.23 [β ] The definition of
lcexp
ignores the condition in definition 1.1.8 that says βIdentifier is any symbol other thanlambda
.β Modify the definition of identifier? to capture this condition. As a hint, remember that any predicate can be used indefinedatatype
, even ones you define.
(define identifier?
(lambda (value)
(and (symbol? value)
(not (eqv? value 'lambda)))))
Exercise 2.24 [β ] Here is a definition of binary trees using
definedatatype
.(definedatatype bintree bintree? (leafnode (num integer?)) (interiornode (key symbol?) (left bintree?) (right bintree?)))
Implement a
bintreetolist
procedure for binary trees, so that(bintreetolist (interiornode 'a (leafnode 3) (leafnode 4)))
returns the list(interiornode a (leafnode 3) (leafnode 4))
(define bintreetolist
(lambda (tree)
(cases bintree tree
[leafnode (num) `(leafnode ,num)]
[interiornode (key left right) (list 'interiornode
key
(bintreetolist left)
(bintreetolist right))])))
Exercise 2.25 [β β ] Use
cases
to writemaxinterior
, which takes a binary tree of integers (as in the preceding exercise) with at least one interior node and returns the symbol associated with an interior node with a maximal leaf sum.> (define tree1 (interiornode 'foo (leafnode 2) (leafnode 3))) > (define tree2 (interiornode 'bar (leafnode 1) tree1)) > (define tree3 (interiornode 'baz tree2 (leafnode 1))) > (maxinterior tree2) foo > (maxinterior tree3) baz
The last invocation of
maxinterior
might also have returnedfoo
, since both thefoo
andbaz
nodes have a leaf sum of 5.
(definedatatype bintreeinfo bintreeinfo?
[leafinfo [num integer?]]
[interiorinfo [sum integer?]
[maxsum integer?]
[maxkey symbol?]])
(define maxinteriorhelper
(lambda (tree)
(cases bintree tree
[leafnode (num)
(leafinfo num)]
[interiornode (key left right)
(let ([leftinfo (maxinteriorhelper left)]
[rightinfo (maxinteriorhelper right)])
(cases bintreeinfo leftinfo
[leafinfo (leftnum)
(cases bintreeinfo rightinfo
[leafinfo (rightnum)
(let ([sum (+ leftnum rightnum)])
(interiorinfo sum
sum
key))]
[interiorinfo (rightsum rightmaxsum rightmaxkey)
(let ([sum (+ leftnum rightsum)])
(if (< sum rightmaxsum)
(interiorinfo sum
rightmaxsum
rightmaxkey)
(interiorinfo sum
sum
key)))])]
[interiorinfo (leftsum leftmaxsum leftmaxkey)
(cases bintreeinfo rightinfo
[leafinfo (rightnum)
(let ([sum (+ leftsum rightnum)])
(if (< sum leftmaxsum)
(interiorinfo sum
leftmaxsum
leftmaxkey)
(interiorinfo sum
sum
key)))]
[interiorinfo (rightsum rightmaxsum rightmaxkey)
(let* ([sum (+ leftsum rightsum)]
[maxsumandkey (if (< leftmaxsum rightmaxsum)
(cons rightmaxsum rightmaxkey)
(cons leftmaxsum leftmaxkey))]
[maxsum (car maxsumandkey)]
[maxkey (cdr maxsumandkey)])
(if (< sum maxsum)
(interiorinfo sum
maxsum
maxkey)
(interiorinfo sum
sum
key)))])]))])))
(define maxinterior
(lambda (tree)
(cases bintreeinfo (maxinteriorhelper tree)
[leafinfo (num) (eopl:error 'maxinterior "~s is not an interior node" tree)]
[interiorinfo (sum maxsum maxkey) maxkey])))
Exercise 2.26 [β β ] Here is another version of exercise 1.33. Consider a set of trees given by the following grammar:
Redbluetree ::= Redbluesubtree Redbluesubtree ::= (rednode
RedbluesubtreeRedbluesubtree
)
Β ::= (bluenode
{Redbluesubtree}^{β})
Β ::= (leafnode Int)
Write an equivalent definition using
definedatatype
, and use the resulting interface to write a procedure that takes a tree and builds a tree of the same shape, except that each leaf node is replaced by a leaf node that contains the number of red nodes on the path between it and the root.
(definedatatype redbluetree redbluetree?
[rednode [lson redbluetree?]
[rson redbluetree?]]
[bluenode [sons (listof redbluetree?)]]
[leafnode [num integer?]])
(define markleaveswithreddepthhelper
(lambda (tree rednum)
(cases redbluetree tree
[rednode (lson rson) (let ([newrednum (+ rednum 1)])
(rednode (markleaveswithreddepthhelper lson newrednum)
(markleaveswithreddepthhelper rson newrednum)))]
[bluenode (sons) (bluenode (map (lambda (son)
(markleaveswithreddepthhelper son rednum))
sons))]
[leafnode (_) (leafnode rednum)])))
(define markleaveswithreddepth
(lambda (tree)
(markleaveswithreddepthhelper tree 0)))
Exercise 2.27 [β ] Draw the abstract syntax tree for the lambda calculus expressions
((lambda (a) (a b)) c) (lambda (x) (lambda (y) ((lambda (x) (x y)) x)))
((lambda (a) (a b)) c)
βββββββββββ
β appexp β
ββββββ¬βββββ
ββββββββ΄ββββββββ
rator rand
βββββββ΄βββββββ ββββββ΄βββββ
β lambdaexp β β varexp β
βββββββ¬βββββββ ββββββ¬βββββ
ββββββ΄βββββ β
boundvar body var
βββ΄ββ ββββββ΄βββββ βββ΄ββ
β a β β appexp β β c β
βββββ ββββββ¬βββββ βββββ
βββββββ΄βββββββ
rator rand
ββββββ΄βββββ ββββββ΄βββββ
β varexp β β varexp β
ββββββ¬βββββ ββββββ¬βββββ
β β
var var
βββ΄ββ βββ΄ββ
β a β β b β
βββββ βββββ
(lambda (x)
(lambda (y)
((lambda (x)
(x y))
x)))
ββββββββββββββ
β lambdaexp β
βββββββ¬βββββββ
ββββββ΄ββββββ
boundvar body
βββ΄ββ βββββββ΄βββββββ
β x β β lambdaexp β
βββββ βββββββ¬βββββββ
ββββββ΄βββββ
boundvar body
βββ΄ββ ββββββ΄βββββ
β y β β appexp β
βββββ ββββββ¬βββββ
ββββββββ΄ββββββββ
rator rand
βββββββ΄βββββββ ββββββ΄βββββ
β lambdaexp β β varexp β
βββββββ¬βββββββ ββββββ¬βββββ
ββββββ΄βββββ β
boundvar body var
βββ΄ββ ββββββ΄βββββ βββ΄ββ
β x β β appexp β β x β
βββββ ββββββ¬βββββ βββββ
βββββββ΄βββββββ
rator rand
ββββββ΄βββββ ββββββ΄βββββ
β varexp β β varexp β
ββββββ¬βββββ ββββββ¬βββββ
β β
var var
βββ΄ββ βββ΄ββ
β x β β y β
βββββ βββββ
Exercise 2.28 [β ] Write an unparser that converts the abstract syntax of an lcexp into a string that matches the second grammar in this section (page 52).
(define identifier? symbol?)
(definedatatype lcexp lcexp?
[varexp [var identifier?]]
[lambdaexp [boundvar identifier?]
[body lcexp?]]
[appexp [rator lcexp?]
[rand lcexp?]])
(define unparselcexp
(lambda (exp)
(cases lcexp exp
[varexp (var) (symbol>string var)]
[lambdaexp (boundvar body)
(stringappend "(lambda ("
(symbol>string boundvar)
") "
(unparselcexp body)
")")]
[appexp (rator rand)
(stringappend "("
(unparselcexp rator)
" "
(unparselcexp rand)
")")])))
Exercise 2.29 [β ] Where a Kleene star or plus (page 7) is used in concrete syntax, it is most convenient to use a list of associated subtrees when constructing an abstract syntax tree. For example, if the grammar for lambdacalculus expressions had been
Lcexp ::= Identifier Β Β varexp (var)
Β ::= (lambda (
{Identifier}^{β})
Lcexp)
Β Β lambdaexp (boundvars body)
Β ::= (
Lcexp {Lcexp}^{β})
Β Β appexp (rator rands)
then the predicate for the
boundvars
field could be(listof identifier?)
, and the predicate for therands
field could be(listof lcexp?)
. Write adefinedatatype
and a parser for this grammar that works in this way.
(define identifier?
(lambda (x)
(and (symbol? x)
(not (eqv? x 'lambda)))))
(definedatatype lcexp lcexp?
[varexp [var identifier?]]
[lambdaexp [boundvars (listof identifier?)]
[body lcexp?]]
[appexp [rator lcexp?]
[rands (listof lcexp?)]])
(define parseexpression
(lambda (datum)
(cond [(identifier? datum) (varexp datum)]
[(pair? datum) (if (eqv? (car datum) 'lambda)
(lambdaexp (cadr datum)
(parseexpression (caddr datum)))
(appexp (parseexpression (car datum))
(map parseexpression (cdr datum))))]
[else (eopl:error 'parseexpression "Invalid expression: ~s" datum)])))
Exercise 2.30 [β β ] The procedure
parseexpression
as defined above is fragile: it does not detect several possible syntactic errors, such as(a b c)
, and aborts with inappropriate error messages for other expressions, such as(lambda)
. Modify it so that it is robust, accepting any sexp and issuing an appropriate error message if the sexp does not represent a lambdacalculus expression.
(define identifier?
(lambda (x)
(and (symbol? x)
(not (eqv? x 'lambda)))))
(definedatatype lcexp lcexp?
[varexp (var identifier?)]
[lambdaexp [boundvar identifier?]
[body lcexp?]]
[appexp [rator lcexp?]
[rand lcexp?]])
(define reporterror
(lambda (expected datum)
(eopl:error 'parseexpression "Expect ~a, but got ~s." expected datum)))
(define parselambdaexpression
(lambda (datum)
(let ([afterlambda (cdr datum)])
(if (pair? afterlambda)
(let ([boundvarlist (car afterlambda)]
[afterboundvarlist (cdr afterlambda)])
(if (pair? boundvarlist)
(let ([boundvar (car boundvarlist)]
[afterboundvar (cdr boundvarlist)])
(if (identifier? boundvar)
(if (null? afterboundvar)
(if (pair? afterboundvarlist)
(let ([body (car afterboundvarlist)]
[afterbody (cdr afterboundvarlist)])
(if (null? afterbody)
(lambdaexp boundvar (parseexpression body))
(reporterror "null after body" afterbody)))
(reporterror "a pair after bound var list" afterboundvarlist))
(reporterror "null after bound var" afterboundvar))
(reporterror "an identifier" boundvar)))
(reporterror "a pair" boundvarlist)))
(reporterror "a pair after lambda" afterlambda)))))
(define parseapplicationexpression
(lambda (datum)
(let ([rator (car datum)]
[afterrator (cdr datum)])
(if (pair? afterrator)
(let ([rand (car afterrator)]
[afterrand (cdr afterrator)])
(if (null? afterrand)
(appexp (parseexpression rator) (parseexpression rand))
(reporterror "null after rand" afterrand)))
(reporterror "a pair after rator" afterrator)))))
(define parseexpression
(lambda (datum)
(cond [(symbol? datum) (if (eqv? datum 'lambda)
(reporterror "an identifier" datum)
(varexp datum))]
[(pair? datum) (if (eqv? (car datum) 'lambda)
(parselambdaexpression datum)
(parseapplicationexpression datum))]
[else (reporterror "a symbol or pair" datum)])))
Exercise 2.31 [β β ] Sometimes it is useful to specify a concrete syntax as a sequence of symbols and integers, surrounded by parentheses. For example, one might define the set of prefix lists by
Prefixlist ::= (
Prefixexp)
Prefixexp ::= Int Β ::= 
PrefixexpPrefixexp
so that
(  3 2  4  12 7)
is a legal prefix list. This is sometimes called Polish prefix notation, after its inventor, Jan Εukasiewicz. Write a parser to convert a prefixlist to the abstract syntax(definedatatype prefixexp prefixexp? (constexp (num integer?)) (diffexp (operand1 prefixexp?) (operand2 prefixexp?)))
so that the example above produces the same abstract syntax tree as the sequence of constructors
(diffexp (diffexp (constexp 3) (constexp 2)) (diffexp (constexp 4) (diffexp (constexp 12) (constexp 7))))
As a hint, consider writing a procedure that takes a list and produces a
prefixexp
and the list of leftover list elements.
(definedatatype prefixexp prefixexp?
[constexp [num integer?]]
[diffexp [operand1 prefixexp?]
[operand2 prefixexp?]])
(define parseprefixexp
(lambda (prefixlist)
(let ([head (car prefixlist)]
[tail (cdr prefixlist)])
(cond [(integer? head) (cons (constexp head) tail)]
[(eqv? head ') (let* ([operand1andrest1 (parseprefixexp tail)]
[operand1 (car operand1andrest1)]
[rest1 (cdr operand1andrest1)]
[operand2andrest2 (parseprefixexp rest1)]
[operand2 (car operand2andrest2)]
[rest2 (cdr operand2andrest2)])
(cons (diffexp operand1 operand2) rest2))]
[else (eopl:error 'parseprefixexp "Bad syntax: ~s." prefixlist)]))))
(define parseprefixlist
(lambda (prefixlist)
(let* ([expandrest (parseprefixexp prefixlist)]
[exp (car expandrest)]
[rest (cdr expandrest)])
(if (null? rest)
exp
(eopl:error 'parseprefixlist "Expect null after prefixexp, but got: ~s." rest)))))
Exercise 3.1 [β ] In figure 3.3, list all the places where we used the fact that ββnββ = n.
Skipped for now.
Exercise 3.2 [β β ] Give an expressed value val β ExpVal for which ββvalββ β val.
Not sure, but maybe when val is constructed using a Bool?
Exercise 3.3 [β ] Why is subtraction a better choice than addition for our single arithmetic operation?
One reason I can think of, is that subtraction is not commutative, that is may not equal to . If our implementation of subtraction is incorrect, we can discover the error quickly.
Exercise 3.4 [β ] Write out the derivation of figure 3.4 as a derivation tree in the style of the one on page 5.
Exercise 3.5 [β ] Write out the derivation of figure 3.5 as a derivation tree in the style of the one on page 5.
Skipped for now.
Exercise 3.6 [β ] Extend the language by adding a new operator minus that takes one argument, n, and returns n. For example, the value of
minus((minus(5), 9))
should be 14.
Solution is implemented here.
Exercise 3.7 [β ] Extend the language by adding operators for addition, multiplication, and integer quotient.
Solution is implemented here.
Exercise 3.8 [β ] Add a numeric equality predicate
equal?
and numeric order predicatesgreater?
andless?
to the set of operations in the defined language.
Solution is implemented here.
Exercise 3.9 [β β ] Add list processing operations to the language, including
cons
,car
,cdr
,null?
andemptylist
. A list should be able to contain any expressed value, including another list. Give the definitions of the expressed and denoted values of the language, as in section 3.2.2. For example,let x = 4 in cons(x, cons(cons((x,1), emptylist), emptylist))
should return an expressed value that represents the list
(4 (3))
.
Solution is implemented here.
Exercise 3.10 [β β ] Add an operation
list
to the language. This operation should take any number of arguments, and return an expressed value containing the list of their values. For example,let x = 4 in list(x, (x,1), (x,3))
should return an expressed value that represents the list
(4 3 1)
.
Solution is implemented here.
Exercise 3.11 [β ] In a real language, one might have many operators such as those in the preceding exercises. Rearrange the code in the interpreter so that it is easy to add new operators.
Solution is implemented here.
Exercise 3.12 [β ] Add to the defined language a facility that adds a
cond
expression. Use the grammarExpression ::=
cond
{Expression==>
Expression}^{β}end
In this expression, the expressions on the lefthand sides of the
==>
βs are evaluated in order until one of them returns a true value. Then the value of the entire expression is the value of the corresponding righthand expression. If none of the tests succeeds, the expression should report an error.
Solution is implemented here.
Exercise 3.13 [β ] Change the values of the language so that integers are the only expressed values. Modify
if
so that the value 0 is treated as false and all other values are treated as true. Modify the predicates accordingly.
Solution is implemented here.
Exercise 3.14 [β β ] As an alternative to the preceding exercise, add a new nonterminal Boolexp of boolean expressions to the language. Change the production for conditional expressions to say
Expression ::=
if
Boolexpthen
Expressionelse
ExpressionWrite suitable productions for Boolexp and implement
valueofboolexp
. Where do the predicates of exercise 3.8 wind up in this organization?
Iβll deal with this one later.
Exercise 3.15 [β ] Extend the language by adding a new operation
Solution is implemented here.
Because print
cause a side effect while our specification framework does not have something to do this.
Exercise 3.16 [β β ] Extend the language so that a
let
declaration can declare an arbitrary number of variables, using the grammarExpression ::=
let
{Identifier=
Expression}^{β}in
ExpressionAs in Schemeβs
let
, each of the righthand sides is evaluated in the current environment, and the body is evaluated with each new variable bound to the value of its associated righthand side. For example,let x = 30 in let x = (x,1) y = (x,2) in (x,y)
should evaluate to 1.
Solution is implemented here.
Exercise 3.17 [β β ] Extend the language with a
let*
expression that works like Schemeβslet*
, so thatlet x = 30 in let* x = (x,1) y = (x,2) in (x,y)
should evaluate to 2.
Solution is implemented here.
Exercise 3.18 [β β ] Add an expression to the defined language:
Expression ::=
unpack
{Identifier}^{β}=
Expressionin
Expressionso that
unpack x y z = lst in ...
bindsx
,y
, andz
to the elements oflst
iflst
is a list of exactly three elements, and reports an error otherwise. For example, the value oflet u = 7 in unpack x y = cons(u,cons(3,emptylist)) in (x,y)
should be 4.
Solution is implemented here.
Exercise 3.19 [β ] In many languages, procedures must be created and named at the same time. Modify the language of this section to have this property by replacing the
proc
expression with aletproc
expression.
Skipped for now.
Exercise 3.20 [β ] In PROC, procedures have only one argument, but one can get the effect of multiple argument procedures by using procedures that return other procedures. For example, one might write code like
let f = proc (x) proc (y) ... in ((f 3) 4)
This trick is called Currying, and the procedure is said to be Curried. Write a Curried procedure that takes two arguments and returns their sum. You can write x + y in our language by writing
(
x, (0,
y))
.
proc (x)
proc (y)
(x, (0, y))
Exercise 3.21 [β β ] Extend the language of this section to include procedures with multiple arguments and calls with multiple operands, as suggested by the grammar
Expression ::=
proc (
{Identifier}^{β(,)})
Expression
Expression ::=(
Expression{Expression}^{β}
)
Solution is implemented here.
Exercise 3.22 [β β β ] The concrete syntax of this section uses different syntax for a builtin operation, such as difference, from a procedure call. Modify the concrete syntax so that the user of this language need not know which operations are builtin and which are defined procedures. This exercise may range from very easy to hard, depending on the parsing technology being used.
Solution is implemented here.
Exercise 3.23 [β β ] What is the value of the following PROC program?
let makemult = proc (maker) proc (x) if zero?(x) then 0 else (((maker maker) (x,1)), 4) in let times4 = proc (x) ((makemult makemult) x) in (times4 3)
Use the tricks of this program to write a procedure for factorial in PROC. As a hint, remember that you can use Currying (exercise 3.20) to define a twoargument procedure
times
.
Value of given program is 12.
The procedure of factorial:
let maketimes = proc (maker)
proc (x)
proc (y)
if zero?(x)
then 0
else ((((maker maker) (x, 1)) y), (0, y))
in let times = (maketimes maketimes)
in let makefact = proc (maker)
proc (x)
if zero?(x)
then 1
else ((times x) ((maker maker) (x, 1)))
in (makefact makefact)
Exercise 3.24 [β β ] Use the tricks of the program above to write the pair of mutually recursive procedures,
odd
andeven
, as in exercise 3.32.
odd
:
let false = zero?(1)
in let true = zero?(0)
in let makeeven = proc (makeeven)
proc (makeodd)
proc (x)
if zero?(x)
then true
else (((makeodd makeeven) makeodd) (x, 1))
in let makeodd = proc (makeeven)
proc (makeodd)
proc (x)
if zero?(x)
then false
else (((makeeven makeeven) makeodd) (x, 1))
in ((makeodd makeeven) makeodd)
even
:
let false = zero?(1)
in let true = zero?(0)
in let makeeven = proc (makeeven)
proc (makeodd)
proc (x)
if zero?(x)
then true
else (((makeodd makeeven) makeodd) (x, 1))
in let makeodd = proc (makeeven)
proc (makeodd)
proc (x)
if zero?(x)
then false
else (((makeeven makeeven) makeodd) (x, 1))
in ((makeeven makeeven) makeodd)
Exercise 3.25 [β ] The tricks of the previous exercises can be generalized to show that we can define any recursive procedure in PROC. Consider the following bit of code:
let makerec = proc (f) let d = proc (x) proc (z) ((f (x x)) z) in proc (n) ((f (d d)) n) in let maketimes4 = proc (f) proc (x) if zero?(x) then 0 else ((f (x,1)), 4) in let times4 = (makerec maketimes4) in (times4 3)
Show that it returns 12.
maketimes4
is a procedure that takes a times4
procedure and returns a times4
procedure. First we convert
maketimes4
to a procedure maker
that takes a maker
and returns a times4
procedure (assume we use f
to
represent maketimes4
):
proc (f)
let maker = proc (maker)
let recuriveproc = (maker maker)
in (f recuriveproc)
in ...
But the code would not work because once we call (maker maker)
, it will first call (maker maker)
which will cause
infinite recursion. We will fix this by wrapping (maker maker)
inside another procedure:
proc (f)
let maker = proc (maker)
proc (x)
let recuriveproc = (maker maker)
in ((f recuriveproc) x)
in ...
Now we get a maker
, we call the maker
with maker
, we will get a recursive version of f
:
proc (f)
let maker = proc (maker)
proc (x)
let recuriveproc = (maker maker)
in ((f recuriveproc) x)
in (maker maker)
Letβs run the program:
let makerec = proc (f)
let maker = proc (maker)
proc (x)
let recuriveproc = (maker maker)
in ((f recuriveproc) x)
in (maker maker)
in let maketimes4 = proc (f)
proc (x)
if zero?(x)
then 0
else ((f (x, 1)), 4)
in let times4 = (makerec maketimes4)
in (times4 3)
Yep, the result is also 12. Although it is a little different than the original one.
Exercise 3.26 [β β ] In our datastructure representation of procedures, we have kept the entire environment in the closure. But of course all we need are the bindings for the free variables. Modify the representation of procedures to retain only the free variables.
Here is a function that filters free variables in the environment:
(define (filterenv env boundvars exp)
(let loop ([result (emptyenv)]
[boundvars boundvars]
[exp exp])
(cases expression exp
[constexp (num) result]
[varexp (var) (if (memv var boundvars)
result
(extendenv var (applyenv env var) result))]
[diffexp (exp1 exp2) (loop (loop result boundvars exp1) boundvars exp2)]
[zero?exp (exp1) (loop result boundvars exp1)]
[ifexp (exp1 exp2 exp3) (loop (loop (loop result
boundvars
exp1)
boundvars
exp2)
boundvars
exp3)]
[letexp (var exp1 body) (loop (loop result boundvars exp1)
(cons var boundvars)
body)]
[procexp (vars body) (loop result (append vars boundvars) body)]
[callexp (rator rands) (let loop2 ([result (loop result boundvars rator)]
[rands rands])
(if (null? rands)
result
(loop2 (loop result boundvars (car rands))
(cdr rands))))])))
Exercise 3.27 [β ] Add a new kind of procedure called a
traceproc
to the language. Atraceproc
works exactly like aproc
, except that it prints a trace message on entry and on exit.
Solution is implemented here.
Exercise 3.28 [β β ] Dynamic binding (or dynamic scoping) is an alternative design for procedures, in which the procedure body is evaluated in an environment obtained by extending the environment at the point of call. For example in
let a = 3 in let p = proc (x) (x,a) a = 5 in (a,(p 2))
the
a
in the procedure body would be bound to 5, not 3. Modify the language to use dynamic binding. Do this twice, once using a procedural representation for procedures, and once using a datastructure representation.
Solution is implemented here.
Only datastructure representation is implemented.
Exercise 3.29 [β β ] Unfortunately, programs that use dynamic binding may be exceptionally difficult to understand. For example, under lexical binding, consistently renaming the bound variables of a procedure can never change the behavior of a program: we can even remove all variables and replace them by their lexical addresses, as in section 3.6. But under dynamic binding, this transformation is unsafe.
For example, under dynamic binding, the procedure
proc (z) a
returns the value of the variablea
in its callerβs environment. Thus, the programlet a = 3 in let p = proc (z) a in let f = proc (x) (p 0) in let a = 5 in (f 2)
returns 5, since
a
βs value at the call site is 5. What iff
βs formal parameter werea
?
The result should be 2.
Exercise 3.30 [β ] What is the purpose of the call to
procval
on the nexttolast line ofapplyenv
?
When we are creating the desired recursive closure, we need an environment containing the closure, but we can not create
the environment directly because we need the closure in order to create the environment. So we delay the creation of
the closure in the environment so that we can create the environment without a closure. Then, when we need to use the
closure, we create it by calling procval
.
Exercise 3.31 [β ] Extend the language above to allow the declaration of a recursive procedure of possibly many arguments, as in exercise 3.21.
Solution is implemented here.
Exercise 3.32 [β β ] Extend the language above to allow the declaration of any number of mututally recursive unary procedures, for example:
letrec even(x) = if zero?(x) then 1 else (odd (x,1)) odd(x) = if zero?(x) then 0 else (even (x,1)) in (odd 13)
Solution is implemented here.
Exercise 3.33 [β β ] Extend the language above to allow the declaration of any number of mutually recursive procedures, each of possibly many arguments, as in exercise 3.21.
Solution is implemented here.
Exercise 3.34 [β β β ] Implement
extendenvrec
in the procedural representation of environments from section 2.2.3.
Skipped for now.
Exercise 3.35 [β ] The representationswe have seen so far are inefficient, because they build a new closure every time the procedure is retrieved. But the closure is the same every time. We can build the closures only once, by putting the value in a vector of length 1 and building an explicit circular structure, like
TODO: Add this figure later.
Hereβs the code to build this data structure.
(define extendenvrec (lambda (pname bvar body savedenv) (let ((vec (makevector 1))) (let ((newenv (extendenv pname vec savedenv))) (vectorset! vec 0 (procval (procedure bvar body newenv))) newenv))))
Complete the implementation of this representation by modifying the definitions of the environment data type and
applyenv
accordingly. Be sure thatapplyenv
always returns an expressed value.
Solution is implemented here.
Exercise 3.36 [β β ] Extend this implementation to handle the language from exercise 3.32.
Solution is implemented here.
Exercise 3.37 [β ] With dynamic binding (exercise 3.28), recursive procedures may be bound by
let
; no special mechanism is necessary for recursion. This is of historical interest; in the early years of programming language design other approaches to recursion, such as those discussed in section 3.4, were not widely understood. To demonstrate recursion via dynamic binding, test the programlet fact = proc (n) add1(n) in let fact = proc (n) if zero?(n) then 1 else *(n,(fact (n,1))) in (fact 5)
using both lexical and dynamic binding. Write the mutually recursive procedures
even
andodd
as in section 3.4 in the defined language with dynamic binding.
Skipped for now.
Exercise 3.38 [β ] Extend the lexical address translator and interpreter to handle
cond
from exercise 3.12.
Solution is implemented here.
Exercise 3.39 [β ] Extend the lexical address translator and interpreter to handle
pack
andunpack
from exercise 3.18.
Solution is implemented here.
Exercise 3.40 [β β ] Extend the lexical address translator and interpreter to handle
letrec
. Do this by modifying the context argument totranslationof
so that it keeps track of not only the name of each bound variable, but also whether it was bound byletrec
or not. For a reference to a variable that was bound by aletrec
, generate a new kind of reference, called anamelessletrecvarexp
. You can then continue to use the nameless environment representation above, and the interpreter can do the right thing with anamelessletrecvarexp
.
Solution is implemented here.
Exercise 3.41 [β β ] Modify the lexical address translator and interpreter to handle
let
expressions, procedures, and procedure calls with multiple arguments, as in exercise 3.21. Do this using a nameless version of the ribcage representation of environments (exercise 2.11). For this representation, the lexical address will consist of two nonnegative integers: the lexical depth, to indicate the number of contours crossed, as before; and a position, to indicate the position of the variable in the declaration.
Solution is implemented here.
Exercise 3.42 [β β β ] Modify the lexical address translator and interpreter to use the trimmed representation of procedures from exercise 3.26. For this, you will need to translate the body of the procedure not
(extendsenv
varsenv
)
, but in a new static environment that tells exactly where each variable will be kept in the trimmed representation.
Solution is implemented here.
Exercise 3.43 [β β β ] The translator can do more than just keep track of the names of variables. For example, consider the program
let x = 3 in let f = proc (y) (y,x) in (f 13)
Here we can tell statically that at the procedure call,
f
will be bound to a procedure whose body is(y,x)
, wherex
has the same value that it had at the procedurecreation site. Therefore we could avoid looking upf
in the environment entirely. Extend the translator to keep track of βknown proceduresβ and generate code that avoids an environment lookup at the call of such a procedure.
Solution is implemented here.
Exercise 3.44 [β β β ] In the preceding example, the only use of
f
is as a known procedure. Therefore the procedure built by the expression proc(y) (y,x)
is never used. Modify the translator so that such a procedure is never constructed.
Solution is implemented here.
Exercise 4.1 [β ] What would have happened had the program been instead
let g = proc (dummy) let counter = newref(0) in begin setref(counter, (deref(counter), 1)); deref(counter) end in let a = (g 11) in let b = (g 11) in (a,b)
The result would been 0. Because counter
rebinds to a new location that has the value 0 every time g is called, the
final value that referenced by counter
will be the same.
Exercise 4.2 [β ] Write down the specification for a
zero?exp
.
Exercise 4.3 [β ] Write down the specification for a
callexp
.
Exercise 4.4 [β β ] Write down the specification for a
begin
expresssion.Expression ::=
begin
Expression{
;
Expression}^{β}end
A
begin
expression may contain one or more subexpressions separated by semicolons. These are evaluated in order and the value of the last is returned.
Exercise 4.5 [β β ] Write down the specification for
list
(exercise 3.10).
Exercise 4.6 [β ] Modify the rule given above so that a
setrefexp
returns the value of the righthand side.
Exercise 4.7 [β ] Modify the rule given above so that a
setrefexp
returns the old contents of the location.
Exercise 4.8 [β ] Show exactly where in our implementation of the store these operations take linear time rather than constant time.
In newref
, length
and append
take linear time, so newref
takes linear time.
In deref
, listref
take linear time, so deref
takes linear time.
In setref!
, setrefinner
loops through the store, which takes linear time, so setref!
takes linear time.
Exercise 4.9 [β ] Implement the store in constant time by representing it as a Scheme vector. What is lost by using this representation?
(define (emptystore)
(vector))
(define thestore 'uninitialized)
(define (getstore)
thestore)
(define (initializestore!)
(set! thestore (emptystore)))
(define (reference? v)
(and (integer? v)
(not (negative? v))))
(define (extendstore store val)
(let* ([storesize (vectorlength store)]
[newstore (makevector (+ storesize 1))])
(let loop ([i 0])
(if (< i storesize)
(let ([val (vectorref store i)])
(vectorset! newstore i val)
(loop (+ i 1)))
(vectorset! newstore i val)))
(cons newstore storesize)))
(define (newref val)
(let* ([newstoreinfo (extendstore thestore val)]
[newstore (car newstoreinfo)]
[newref (cdr newstoreinfo)])
(set! thestore newstore)
newref))
(define (deref ref)
(vectorref thestore ref))
(define (reportinvalidreference ref store)
(eopl:error 'setref
"illegal reference ~s in store ~s"
ref
store))
(define (setref! ref val)
(if (and (reference? ref)
(< ref (vectorlength thestore)))
(vectorset! thestore ref val)
(reportinvalidreference ref thestore)))
Note that newref
still takes linear time. It is possible to implement the store that allocates locations in constant
time on average by preallocating more locations in advance, but it is a little complecated, so Iβll just choose the easy
way to implement the store.
As for the disadvantages of using a Scheme vector to implement the store, may be sharing values between stores becomes more difficult.
Exercise 4.10 [β ] Implement the
begin
expression as specified in exercise 4.4.
The reference implementation already implemented the begin
expression, so Iβll just skip this one.
Exercise 4.11 [β ] Implement
list
from exercise 4.5.
Solution is implemented here.
Exercise 4.12 [β β β ] Our understanding of the store, as expressed in this interpreter, depends on themeaning of effects in Scheme. In particular, it depends on us knowing when these effects take place in a Scheme program. We can avoid this dependency by writing an interpreter that more closely mimics the specification. In this interpreter,
valueof
would return both a value and a store, just as in the specification. A fragment of this interpreter appears in figure 4.6. We call this a storepassing interpreter. Extend this interpreter to cover all of the language EXPLICITREFS.Every procedure that mightmodify the store returns not just its usual value but also a new store. These are packaged in a data type called
answer
. Complete this definition ofvalueof
.
Solution is implemented here.
Also, what is applystore
in the reference implementation?
Exercise 4.13 [β β β ] Extend the interpreter of the preceding exercise to have procedures of multiple arguments.
Solution is implemented here.
Exercise 4.14 [β ] Write the rule for
let
.
Exercise 4.15 [β ] In figure 4.8, why are variables in the environment bound to plain integers rather than expressed values, as in figure 4.5?
Because we know for sure that the denoted values will all be references, so plain integers are sufficient to represent the location info we need.
Exercise 4.16 [β ] Now that variables are mutable, we can build recursive procedures by assignment. For example
letrec times4(x) = if zero?(x) then 0 else ((times4 (x,1)), 4) in (times4 3)
can be replaced by
let times4 = 0 in begin set times4 = proc (x) if zero?(x) then 0 else ((times4 (x,1)), 4); (times4 3) end
Trace this by hand and verify that this translation works.
First we allocate a new location for the number 0, then we bind times4
to the location. After we setting times4
to
the procedure, the location pointed by times4
contains the procedure closure. In the enclosed environment of the
procedure, times4
also points to the procedure so the procedure can call itself recursively.
Exercise 4.17 [β β ] Write the rules for and implement multiargument procedures and
let
expressions.
Exercise 4.18 [β β ] Write the rule for and implement multiprocedure
letrec
expressions.
Exercise 4.19 [β β ] Modify the implementation of multiprocedure
letrec
so that each closure is built only once, and only one location is allocated for it. This is like exercise 3.35.
Solution is implemented here.
Exercise 4.20 [β β ] In the language of this section, all variables are mutable, as they are in Scheme. Another alternative is to allow both mutable and immutable variable bindings:
 ExpVal = Int + Bool + Proc
 DenVal = Ref(ExpVal) + ExpVal
Variable assignment should work only when the variable to be assigned to has a mutable binding. Dereferencing occurs implicitly when the denoted value is a reference.
Modify the language of this section so that
let
introduces immutable variables, as before, but mutable variables are introduced by aletmutable
expression, with syntax given byExpression ::=
letmutable
Identifier=
Expressionin
Expression
Solution is implemented here.
Exercise 4.21 [β β ] We suggested earlier the use of assignment to make a program more modular by allowing one procedure to communicate information to a distant procedure without requiring intermediate procedures to be aware of it. Very often such an assignment should only be temporary, lasting for the execution of a procedure call. Add to the language a facility for dynamic assignment (also called fluid binding) to accomplish this. Use the production
Expression ::= setdynamic
Identifier=
Expressionduring
ExpressionΒ setdynamicexp (
varexp_{1}
body
)
The effect of the
setdynamic
expression is to assign temporarily the value of exp_{1} to var, evaluate body, reassign var to its original value, and return the value of body. The variable var must already be bound. For example, inlet x = 11 in let p = proc (y) (y,x) in (setdynamic x = 17 during (p 22), (p 13))
the value of
x
, which is free in procedurep
, is 17 in the call(p 22)
, but is reset to 11 in the call(p 13)
, so the value of the expression is 5  2 = 3.
Solution is implemented here.
Exercise 4.22 [β β ] So far our languages have been expressionoriented: the primary syntactic category of interest has been expressions and we have primarily been interested in their values. Extend the language to model the simple statementoriented language whose specification is sketched below. Be sure to Follow the Grammar by writing separate procedures to handle programs, statements, and expressions.
Values As in IMPLICITREFS.
Syntax Use the following syntax:
Program ::= Statement Statement ::= Identifier =
ExpressionΒ ::= Β ::= {
{Statement}^{β(;)}}
Β ::= if
ExpressionStatement
Statement
Β ::= while
ExpressionStatement
Β ::= var
{Identifier}^{β(,)};
StatementThe nonterminal Expression refers to the language of expressions of IMPLICITREFS, perhaps with some extensions.
Semantics A program is a statement. A statement does not return a value, but acts by modifying the store and by printing.
Assignment statements work in the usual way. A print statement evaluates its actual parameter and prints the result. The
if
statement works in the usual way. A block statement, defined in the last production for Statement, binds each of the declared variables to an uninitialized reference and then executes the body of the block. The scope of these bindings is the body.Write the specification for statements using assertions like
Examples Here are some examples.
(run "var x,y; {x = 3; y = 4; print +(x,y)} % Example 1") 7 (run "var x,y,z; {x = 3; % Example 2 y = 4; z = 0; while not(zero?(x)) {z = +(z,y); x = (x,1)}; print z}") 12 (run "var x; {x = 3; % Example 3 print x; var x; {x = 4; print x}; print x}") 3 4 3 (run "var f,x; {f = proc(x,y) *(x,y); % Example 4 x = 3; print (f 4 x)}") 12
Example 3 illustrates the scoping of the block statement.
Example 4 illustrates the interaction between statements and expressions. A procedure value is created and stored in the variable
f
. In the last line, this procedure is applied to the actual parameters 4 andx
; sincex
is bound to a reference, it is dereferenced to obtain 3.
Solution is implemented here.
Specification for statements:
Exercise 4.23 [β ] Add to the language of exercise 4.22
read
statements of the formread
var. This statement reads a nonnegative integer from the input and stores it in the given variable.
Solution is implemented here.
Exercise 4.24 [β ] A
dowhile
statement is like awhile
statement, except that the test is performed after the execution of the body. Adddowhile
statements to the language of exercise 4.22.
Solution is implemented here.
Exercise 4.25 [β ] Extend the block statement of the language of exercise 4.22 to allow variables to be initialized. In your solution, does the scope of a variable include the initializer for variables declared later in the same block statement?
Solution is implemented here.
No, the scope of a variable does not include the initializer for variables declared later in the same block statement.
Exercise 4.26 [β β β ] Extend the solution to the preceding exercise so that procedures declared in a single block aremutually recursive. Consider restricting the language so that the variable declarations in a block are followed by the procedure declarations.
Solution is implemented here.
Exercise 4.27 [β β ] Extend the language of the preceding exercise to include subroutines. In our usage a subroutine is like a procedure, except that it does not return a value and its body is a statement, rather than an expression. Also, add subroutine calls as a new kind of statement and extend the syntax of blocks so that they may be used to declare both procedures and subroutines. How does this affect the denoted and expressed values? What happens if a procedure is referenced in a subroutine call, or vice versa?
Solution is implemented here.
Denoted values does not change, expressed values now conain a subval
variant.
Error will happen if procedure is referenced in a subroutine call, or vice versa.
Exercise 4.28 [β β ] Write down the specification rules for the five mutablepair operations.
Exercise 4.29 [β β ] Add arrays to this language. Introduce new operators
newarray
,arrayref
, andarrayset
that create, dereference, and update arrays. This leads to
 ArrVal = (Ref(ExpVal))^{β}
 ExpVal = Int + Bool + Proc + ArrVal
 DenVal = Ref(ExpVal)
Since the locations in an array are consecutive, use a representation like the second representation above. What should be the result of the following program?
let a = newarray(2,99) p = proc (x) let v = arrayref(x,1) in arrayset(x,1,(v,1)) in begin arrayset(a,1,0); (p a); (p a); arrayref(a,1) end
Here
newarray(2,99)
is intended to build an array of size 2, with each location in the array containing 99.begin
expressions are defined in exercise 4.4. Make the array indices zerobased, so an array of size 2 has indices 0 and 1.
Solution is implemented here.
The result of that program should be 2.
Exercise 4.30 [β β ] Add to the language of exercise 4.29 a procedure
arraylength
, which returns the size of an array. Your procedure should work in constant time. Make sure thatarrayref
andarrayset
check to make sure that their indices are within the length of the array.
Solution is implemented here.
Exercise 4.31 [β ] Write out the specification rules for CALLBYREFERENCE.
Skipped for now.
Exercise 4.32 [β ] Extend the language CALLBYREFERENCE to have procedures of multiple arguments.
Solution is implemented here.
Exercise 4.33 [β β ] Extend the language CALLBYREFERENCE to support callbyvalue procedures as well.
Solution is implemented here.
Exercise 4.34 [β ] Add a callbyreference version of
let
, calledletref
, to the language. Write the specification and implement it.
Solution is implemented here.
Exercise 4.35 [β β ] We can get some of the benefits of callbyreference without leaving the callbyvalue framework. Extend the language IMPLICITREFS by adding a new expression
Expression ::= ref
IdentifierΒ refexp (var)
This differs from the language EXPLICITREFS, since references are only of variables. This allows us to write familiar programs such as
swap
within our callbyvalue language. What should be the value of this expression?let a = 3 in let b = 4 in let swap = proc (x) proc (y) let temp = deref(x) in begin setref(x,deref(y)); setref(y,temp) end in begin ((swap ref a) ref b); (a,b) end
Here we have used a version of
let
with multiple declarations (exercise 3.16). What are the expressed and denoted values of this language?
Solution is implemented here.
Here we have used a version of
let
with multiple declarations (exercise 3.16).
No, you have not.
The value of the given expression should be 1.
Expressed values now contains reference values, and denoted values still only contains references.
Exercise 4.36 [β ] Most languages support arrays, in which case array references are generally treated like variable references under callbyreference. If an operand is an array reference, then the location referred to, rather than its contents, is passed to the called procedure. This allows, for example, a swap procedure to be used in commonly occurring situations in which the values in two array elements are to be exchanged. Add array operators like those of exercise 4.29 to the callbyreference language of this section, and extend
valueofoperand
to handle this case, so that, for example, a procedure application like((swap arrayref(a, i)) arrayref(a, j))
will work as expected. What should happen in the case of
((swap arrayref(a, arrayref(a, i))) arrayref(a, j))
?
Solution is implemented here.
((swap arrayref(a, arrayref(a, i))) arrayref(a, j))
will swap the element indexed by the value of arrayref(a, i)
with the element indexed by j
.
Exercise 4.37 [β β ] Callbyvalueresult is a variation on callbyreference. In callbyvalueresult, the actual parameter must be a variable. When a parameter is passed, the formal parameter is bound to a new reference initialized to the value of the actual parameter, just as in callbyvalue. The procedure body is then executed normally. When the procedure body returns, however, the value in the new reference is copied back into the reference denoted by the actual parameter. This may be more efficient than callbyreference because it can improve memory locality. Implement callbyvalueresult and write a program that produces different answers using callbyvalueresult and callbyreference.
Solution is implemented here.
This program produces 4 using callbyvalueresult while it produces 3 using callbyreference.
let f = proc (x)
begin set x = 3;
4
end
in let x = 5
in begin (f x);
x
end
Exercise 4.38 [β ] The example below shows a variation of exercise 3.25 that works under callbyneed. Does the original program in exercise 3.25 work under callbyneed? What happens if the program below is run under callbyvalue? Why?
let makerec = proc (f) let d = proc (x) (f (x x)) in (f (d d)) in let maketimes4 = proc (f) proc (x) if zero?(x) then 0 else ((f (x,1)), 4) in let times4 = (makerec maketimes4) in (times4 3)
Yes, the original program in exercise 3.25 works under callbyneed.
And the program above will loop infinitely under callbyvalue, because in line 3, (d d)
calls d
with itself, and
when d
is called, it calls its argument x
with x
, where x
is d
itself. So (d d)
leads to another call to
(d d)
which leads to infinite loop.
Exercise 4.39 [β ] In the absence of effects, callbyname and callbyneed always give the same answer. Construct an example in which callbyname and callbyneed give different answers.
let x = 0
in let f = proc (y)
begin y;
y
end
in (f begin set x = (x, 1);
x
end)
The program above should produce 1 in callbyneed and 2 in callbyname.
Exercise 4.40 [β ] Modify
valueofoperand
so that it avoids making thunks for constants and procedures.
Solution is implemented here.
Exercise 4.41 [β β ] Write out the specification rules for callbyname and callbyneed.
Skipped for now.
Exercise 4.42 [β β ] Add a lazy
let
to the callbyneed interpreter.
Solution is implemented here.
Exercise 5.1 [β ] Implement this data type of continuations using the procedural representation.
Solution is implemented here.
Exercise 5.2 [β ] Implement this data type of continuations using a datastructure representation.
Solution is implemented here.
Exercise 5.3 [β ] Add
let2
to this interpreter. Alet2
expression is like alet
expression, except that it defines exactly two variables.
Solution is implemented here.
Exercise 5.4 [β ] Add
let3
to this interpreter. Alet3
expression is like alet
expression, except that it defines exactly three variables.
Solution is implemented here.
Exercise 5.5 [β ] Add lists to the language, as in exercise 3.9.
Solution is implemented here.
Exercise 5.6 [β β ] Add a
list
expression to the language, as in exercise 3.10. As a hint, consider adding two new continuationbuilders, one for evaluating the first element of the list and one for evaluating the rest of the list.
Solution is implemented here.
Exercise 5.7 [β β ] Add multideclaration
let
(exercise 3.16) to this interpreter.
Solution is implemented here.
Exercise 5.8 [β β ] Add multiargument procedures (exercise 3.21) to this interpreter.
Solution is implemented here.
Exercise 5.9 [β β ] Modify this interpreter to implement the IMPLICITREFS language. As a hint, consider including a new continuationbuilder
(setrhscont env var cont)
.
Solution is implemented here.
Exercise 5.10 [β β ] Modify the solution to the previous exercise so that the environment is not kept in the continuation.
Not all environments can be removed from continuations. For example, I cannot think of a way to remove the environment in the continuation of the bound expression.
Solution is implemented here.