Assignment #8: (Not for submission) Turbo Toy

Out: Wednesday, April 23rd   Due: Wednesday, April 23rd, 0:00midnight



Administrative

Work and submission is with the same pairs. (As usual, let me know if you want to switch partners.)

You will need to have an updated course plugin which has a specific level for this assignment. Also, read carefully the recent lecture notes before you begin your work.

This assignment is not difficult — you will basically apply the changes that were discussed in class to the Toy language implementation, and then extend it with two new features. To begin your work, get the toy.scm file which should be used as the basis for your work. You will implement a recursive binder called bindrec, a special form for variable assignment, multiple body expressions, and functions that receive their arguments by reference. The next assignment will follow-up on this one.

The grade value of each part is indicated in the following sections. If you did not manage to do some parts, indicate so clearly at the top of your submission file.



Boxes [5%]

As discussed in class, the first step toward implementing recursive environments is changing the implementation of environments so they hold boxes of values instead of plain values. Begin by changing the type — you need to change the frame? definition so instead of VAL? it uses (box-of VAL?), do this and fix the rest of the code so it works with the new implementation (including changing the global environment).

As we have seen, this is a pretty easy task, but to make it even easier, here's a summary of things you should do after the above change:

Completing this part is needed for the rest of the assignment, which will build up on it.



Variable Mutation [20%]

Now that we have an environment where names are associated with mutable boxes, we can use that to implement variable mutation. To implement this, you will extend the language with a new special form: set!. The syntax of this form is: {set! <id> <TOY>}. Add this to the BNF, to the TOY type definition — use Set for the name of the new variant, and to the parse code. To implement the semantics of the new form, add a Set case to the eval function which will use lookup to retrieve the box that holds the value of the named identifier, then evaluate the expression and use set-box! to store that in the box.

As discussed in class, when you implement the semantics of set!, you will have to decide what value to return as a result of evaluating these forms. The three choices that we have seen in class are:

The last option is the most strict, but it helps keeping things clear so we will use it.

In class, we have used a random number value for that, but that was a kludge. To do this properly, you need a new kind of value that is absolutely useless: add a new BogusV variant to the VAL type definition (it should not consume any inputs), and use that as the return value for evaluating Set syntaxes. As a minor optimization, define bogus as a BogusV value and use that instead of creating new bogosities every time one is needed.

Be sure to add some test cases to check that your modification works. This will be tricky at this point, since bind and fun bodies have just one expression. Hint: you can bind a name to an expression, and never use it. For example, in Scheme:

(let ([x (display "foo")]) 2)
and you can use a similar approach for multiple expressions.



Recursive Binders [25%]

The next extension is bindrec: a recursive binder form, which will be implemented as shown in class. First, add it to the BNF, it looks just like bind:

<TOY> ::= ... | { bind {{ <id> <TOY> } ... } <TOY>} | { bindrec {{ <id> <TOY> } ... } <TOY>} | ...
and continue by extending the TOY type definition with a new BindRec variant.

The next step is to extend the parser: do not follow the bad cut-and-paste habits that were used in class, instead use the fact that parsing a bindrec is exactly the same as parsing a bind. The new code in parse-sexpr should look like this:

[(cons (or 'bind 'bindrec) more) (match sexpr [(list binder (list (list (symbol: names) nameds) ...) body) (let ([binder Bind]) (binder names (map parse-sexpr nameds) (parse-sexpr body)))] [(cons binder more) (error 'parse-sexpr "bad `bind' syntax in ~s" sexpr)])]
where the two places that need to be modified are marked in red.

Now to the important part: the change to eval which should implement the semantics of the new form. To make sure that your implementation is correct, add this test case:

(test (run "{bindrec {{fact {fun {n} {if {= 0 n} 1 {* n {fact {- n 1}}}}}}} {fact 5}}") => 120)
As shown in class, you will need to write a new function, extend-rec,
;; extend-rec : (Listof Symbol) (Listof TOY) ENV -> ENV ;; extends an environment with a new recursive frame. (define (extend-rec names exprs env) ...)
that consumes (unevaluated) expressions, creates the new environment with the identifiers bound to temporary values, then evaluates the expressions in the new environment and modifies the bindings appropriately. Instead of using some random number value for the temporary bindings, use the above bogus that is bound to an instance of BogusV.

In general, there are various approaches that you can use to correctly implement extend-rec — the actual implementation is up to you. A few Scheme features that we have seen in class and can be useful for your code are:



Multiple Body Expressions [25%]

Given that our language has side-effects, it now makes sense to have multiple expressions in places where a ‘body’ expression is expected. There are three such places now: bind, bindrec, fun.

To implement this:

To test your code for both this and mutation, use these two tests:
(test (run "{bind {{make-counter {fun {} {bind {{c 0}} {fun {} {set! c {+ 1 c}} c}}}}} {bind {{c1 {make-counter}} {c2 {make-counter}}} {* {c1} {c1} {c2} {c1}}}}") => 6) (test (run "{bindrec {{foo {fun {} {set! foo {fun {} 2}} 1}}} {+ {foo} {* 10 {foo}}}}") => 21)



Reference Function Arguments [25%]

Finally, you will extend the language so that it is possible to pass variables by reference, instead of the usual “by value”. In some languages, it is possible to mark some variables as “by reference” with some keyword (‘&’ in C++, var in Pascal, and ByRef in Visual Basic). C++ programmers that know C too can usually tell you that if you look under the hood, a by-reference argument is really just a pointer, and we can do the same with boxes now. (Note for C programmers: you can view a box as a pointer to its value.)

To make this a little easier we will not use a special mark for arguments. Instead, we will add a new kind of “by reference” functions: rfun — begin by adding this to the BNF (same as fun), and to the TOY type definition (as an RFun variant). Then modify the parser so it generates this new syntax type when seeing an rfun input: do not copy the code, modify the existing code for fun in a similar way that the bind code was generalized above. You will also need to deal with evaluating RFun syntaxes — make them evaluate just like Funs, but the resulting closure should be distinguishable from plain closures: so add a new kind of closure values (RFunV) to the VAL type definition, and use this new constructor when evaluating an RFun syntax.

The last step is the fun part (“fun”, not “fun”): applying RFun values. The first step is to separate the extend functionality into two parts: factor out the code that generates a new frame into its own function — extend-boxes:

;; extend-boxes : (Listof Symbol) (Listof (Box VAL)) ENV -> ENV ;; extends an environment with a new frame holding the given boxes. (define (extend-boxes names boxes env) (if (= (length names) (length boxes)) (FrameEnv some code here env) ;; this error can be caused by a call to `extend' too (error 'extend-boxes "arity mismatch for names: ~s" names))) ;; extend : (Listof Symbol) (Listof VAL) ENV -> ENV ;; extends an environment with a new frame. (define (extend names values env) (extend-boxes names some code here env))
(Fill in the above code from the old extend) and add a new case for calling an RFunV closure:
[(RFunV names body fun-env) (if (andmap Id? arg-exprs) (let ([boxes your code here]) (eval-body body your code here)) (error 'eval "ref-functions expect only identifiers"))]
The first empty code slot should use lookup and Id-name, and the second slot should use extend-boxes. What this will do is evaluate the function body in an environment where all input variables are aliased to the argument names. Also, make the code not evaluate the function arguments unnecessarily (you will not need to evaluate the arguments when calling a ref-function).

Using this new functionality, you can play with some examples and make them into tests. Here's one example that defines swap:

(test (run "{bind {{swap! {rfun {x y} {bind {{tmp x}} {set! x y} {set! y tmp}}}} {a 1} {b 2}} {swap! a b} {+ a {* 10 b}}}") => 12)