As we saw above, there is an element of call-by-reference when Scheme passes structured data. We will exploit this feature to create a Scheme style that is essentially call-by-reference. A convenient data structure for us to use will be Scheme's box. It's rather like a cons box, but it only has one field. It's just like the cell that we used when we implemented assignment in our interpreter last semester. Here are the constructor, access and update functions provided by Chez Scheme for boxes:
Boxes set up an extra level of indirection. They are commonly used to implement call-by-reference semantics; that is how we shall use them here.(box a) ;; where a is any object, returns a new box containing a (unbox b) ;; where b is a box, returns the contents of b (set-box! b a) ;; returns unspecified, but the contents of b are ;; updated so that box b contains object a
We begin by introducing some new syntax. When you write Scheme code using the new syntax, you will be programming in an apparently imperative language. Two imperative aspects of the "new" language are:
We begin by introducing syntax for declaring variables. We will use the keyword VAR, which is how variables are declared in Pascal. When we write
(VAR foo 23)
we are declaring a variable named foo with an initial value of 23, to which we may later assign new values. You can think of foo as a pointer to an area in memory containing the value 23. To implement this, we use the box feature of Chez Scheme.
(extend-syntax (VAR) [(VAR name initval) (define name (box initval))])
As we mentioned above, a box is rather like a cons box with only one field. You can modify the contents of that field using the command set-box! and you can access the value via the command unbox. To further pursue our analogy with real imperative languages (particularly Pascal, the various Modulae, and Ada), we provide macros written with extend-syntax to let us use := for assignment and ^ for dereferencing.
(extend-syntax (:=) [(:= var val) (set-box! var val)]) (extend-syntax (^) [(^ var) (unbox var)])
Here is a transcript showing how to use the syntax we've introduced so far.
> (VAR x 2) > (VAR y 4) > (:= x (+ (^ x) (^ y))) > (^ x) 6 > (^ y) 4 >
At the cost of a little clumsiness of notation, we have now a programming language that has imperative features. To continue in the same vein, let us add syntax so that you can write while loops in the imperative style:
(extend-syntax (while) ((while condition body ...) (let ((while-body (lambda () body ...)) (while-cond (lambda () condition))) (letrec ((loop (lambda () (if (while-cond) (begin (while-body) (loop)) 'done)))) (loop)))))
You can use this, for example, as follows:
(VAR x 10) (while (> (^ x) 0) (printf "x is ~s~%" (^ x)) (:= x (- (^ x) 1)))
With the understanding that we will now be working with VARs, rather than real Scheme variables, we now have a pass-by- reference language. If we chose, we could make this explicit by using the name proc-by-ref instead of lambda
(extend-syntax (proc-by-ref) [(proc-by-ref (id ...) body ...) (lambda (id ...) body ...)])
An important observation here is that proc-by-ref is no different from ordinary lambda -- the language difference comes about from our understanding that we would deal only with boxed values. The new keyword proc-by-ref is identical to lambda in all respects. We only use the syntax proc-by-ref to emphasize what it is we are doing. When we pass a box to a procedure, we give that procedure the ability to modify the contents of our own variable. Procedures can now execute imperative side effects. Before we start looking at examples, let us introduce one last piece of additional syntax. Imperative languages usually like to have procedures declare their own local variables. Here is some syntax, similar to let, that allows us do that here in a by-reference way. It's followed by an example procedure that employs pass-by-reference.
(extend-syntax (LOCALVAR) [(LOCALVAR ([name initval] ...) body ...) (let ([name (box initval)] ...) body ...)]) (define sumto (proc-by-ref (n) (LOCALVAR ([i 0] [result 0]) (while (>= (^ n) (^ i)) (:= result (+ (^ result) (^ i))) (:= i (+ 1 (^ i)))) (^ result))))
will return the value 78, but will cause no discernable changes.(VAR foo 12) (sumto foo)
What would happen if we replaced the last line (^ result) of sumto by (:= n result)?
Make a copy of the code and add comments to explain what the code is doing and how.(VAR foo 23) (VAR goo 94) (define swap (proc-by-ref (a b) (LOCALVAR ([temp 'irrelevant]) (:= temp (^ a)) (:= a (^ b)) (:= b (^ temp))))) (define swap2 (proc-by-ref (a b) (:= a (+ (^ a) (^ b))) (:= b (- (^ a) (^ b))) (:= a (- (^ a) (^ b)))))
Then write a paragraph explaining if there is any difference between what swap does and what swap2 does. In particular, try (swap foo goo) and (swap2 foo goo); and test the resulting values of foo and goo.
Then write a paragraph explaining which of swap, swap2 you would prefer to use and why.
(define afun (proc-by-ref (x) (LOCALVAR ((y 1)) (while (> (^ x) 0) (:= y (* (^ y) (^ x))) (:= x (- (^ x) 1))) (^ y)))) (VAR n 5) (VAR result 'irrelevant) (:= result (afun n)) (printf "~s! = ~s~%" (^ n) (^ result))
Can you discern what the beginning programmer hoped would be the output? How did the actual output differ from this? Write a short paragraph explaining to the beginning programmer the nature of the error.