CS 22
Clab 28: Infinite Streams with recursion and The Meta-circular evaluator I
Last clab, we used delay and force to build a small library
of useful procedures. By using stream-filter, we were able
to construct a stream of all odd integers and a stream of
all even integers. If you didn't finish this, you can
get my version at: /home/cfk/pub/cs22/week9/clab27.scm.
Another way to define the even integers is to construct
them
from the stream of integers by adding each element to itself. Define and
think about
add-streams and evenstream.
;;; assumes that s1 and s2 are streams of numbers. add-streams
;;; returns a stream that consists of the sums of corresponding
;;; elements in s1 and s2. If one of the streams run out before
;;; the other, its contribution to the subsequent sums is zero.
(define (add-streams s1 s2)
(cond
((stream-null? s1) s2)
((stream-null? s2) s1)
(else
(cons
(+ (stream-car s1) (stream-car s2))
(delay (add-streams (stream-cdr s1) (stream-cdr s2)))))))
;;; generates a stream of even positive integers
(define evenstream (add-streams ints ints))
Test them by evaluating:
(stream->list 40 evenstream)
which forces the first 40 elements out of evenstream.
Combining infinite streams and recusive definitions can be really
mind-blowing. Kids, do not try this at home without your
instructor present! Read the bottom of page 328 and page 329 twice.
Then evaluate and think about:
;;; fibs generates a stream of fibonacci numbers
(define fibs
(cons 0
(delay
(cons 1
(delay
(add-streams
(stream-cdr fibs) fibs))))))
Try:
fibs
(stream->list 50 fibs)
which should show the first 50 Fibonocci numbers. Fibs has a promise to
deliver any Fibonocci number (there may eventually be a
software/hardware limit). It is appropriate to think of fibs as an
infinite stream of Fibonocci numbers.
Now that we have a couple of streams and filters, you should be able
to write a fairly simple expression that will display the 7th odd
Fibonocci number.
Do so and call me over to see it.
Soon, when we write our own scheme interpreter, we will be able to
extend our interpreter by adding the special forms:
force, delay, and cons-stream. Then, in our interpreter, we can use
cons-stream and code from the text.
As part of your homework for Wednesday, do exercise 3.64 on page 338.
Here is sqrt-stream from top of page 335.
(define (sqrt-stream x)
(cons-stream 1.0
(stream-map (lambda (guess)
(sqrt-improve guess x))
(sqrt-stream x))))
You'll have to use our work-around
to define sqrt-stream. But if you pull the whole thing off, you should
be able to get something like:
> (msqrt 10000 0.001)
100.0
> (msqrt 1000000 0.001)
1000.0000000000118
>
The Meta-circular evaluator I
SICP takes an appropriate top-down approach to defining the minievaluator.
By using abstraction barriers, things like bindings and frames can be
represented in different ways without affecting the higher level code
in the evaluator. Today we will begin with one possible way of representing
a frame. It is not the way used in sicp but it is fairly straight-forward.
For now, a binding is a dotted pair so (cat . 4) is an example of a
binding. The
variable cat is
bound to the value 4. Make a binding by evaluating:
(define mybind (cons 'cat 4))
You can check it:
>mybind
(cat . 4)
Now define binding-variable and binding-value and test them:
;;; given a binding returns the variable bound
(define (binding-variable binding)
(car binding))
;;; given a binding returns the value bound
(define (binding-value binding)
(cdr binding))
For example:
> mybind
(cat . 4)
> (binding-variable mybind)
cat
> (binding-value mybind)
4
Now define set-binding-value!
;;; given a binding changes the value-part to the parameter value
;;; and returns the new binding pair
(define (set-binding-value! binding value)
(set-cdr! binding value))
test it:
mybind
(cat . 4)
(set-binding-value! mybind 755)
mybind ;;;cat is now bound to new value
(cat . 755)
The procedure make-binding gives us an mnemonic procedure to make a
binding. Define it now:
(define (make-binding variable value)
(cons variable value))
and then evaluate:
(make-binding 'x 17).
For now a frame will be alist of bindings. So we can make a frame by
evaluating:
(define myframe (list (make-binding 'x 17)
(make-binding 'cat 10)
(make-binding 'p 3)))
Once we have a frame, we can seek a binding by using binding-in-frame.
Define
(define myframe (list (make-binding 'x 17)
(make-binding 'cat 10)
(make-binding 'p 3)))
(define no-binding '())
;;;this assq is like a system primitive but this one returns
;;; '() if there is no match
(define (assq key bindings)
(cond ((null? bindings) no-binding)
((eq? key (binding-variable (car bindings))) (car bindings))
(else (assq key (cdr bindings)))))
;;;Given the frame specified by the last parameter, binding-in-frame
;;; returns the binding for the variable var in frame if var is
;;; bound there. Otherwise returns '() = no-binding.
(define (binding-in-frame var frame)
(assq var frame))
then try the following:
> myframe
((x . 17) (cat . 10) (p . 3))
> (binding-in-frame 'cat myframe)
(cat . 10)
> (binding-value (binding-in-frame 'cat myframe))
10
So, let's write a small evaluator. This will only work for quoted
expressions written
with the quote spelled out, eg. (quote (a 3 )) not '(a 3), or simple
variable names.
Define:
(define (found-binding? b)
(not (eq? b no-binding)))
;;; the following are not quite what we are after but they are a
;;;start
;;;returns the value of variable var in frame
(define (lookup-variable-value var frame)
(let ((b (binding-in-frame var frame)))
(if (found-binding? b)
(binding-value b)
(error "Unbound variable" var))))
(define atom? (lambda (x)
(not (pair? x))))
;;;returns #t if exp is a quote expression; returns #f otherwise
(define (quoted? exp)
(if (atom? exp)
#f
(eq? (car exp) 'quote)))
;;; if exp is a quoted expression, returns its value
(define (text-of-quotation exp) (cadr exp))
;;; returns #t if exp is a simple variable name; returns #f otherwise
(define (variable? exp) (symbol? exp))
Think
about what they do as you define them. You may want to try them each.
Now define mini-eval.
;;; a really mini-mini eval. evaluates quoted expressions and
;;; simple variables only in one frame. The expression is
;;; parameter exp and the frame is parameter env.
(define (mini-eval exp env)
(cond
((quoted? exp) (text-of-quotation exp))
((variable? exp) (lookup-variable-value exp env))
(else (error "unknown expression--mini-eval" exp))))
Try it out. For example try:
> myframe
((x . 17) (cat . 10) (p . 3))
> (mini-eval 'cat myframe)
10
> (mini-eval '(quote (a b c)) myframe)
(a b c)
> (mini-eval 'p myframe)
3
> (mini-eval '(car x) myframe)
unknown expression--mini-eval (car x)
>
Let's review where we are. A binding is a dotted pair so (cat . 4) is
an example of a binding. We have defined functions to construct and
access bindings (make-binding, binding-variable, binding-value).
A frame is a list of bindings. Once we have a frame, we can access
it by using the functions: binding-in-frame, found-binding?,
lookup-variable-value. In order to define a really small evaluator,
we introduced: quoted?, text-of-quotation, variable?, then we wrote a
mini-evaluator.
Important note: eval and apply are built in Scheme primitives. In
order not to clash with them, I will use mini-eval wherever the text
uses bare eval and I will use mini-apply where the text uses bare
apply.
Even though our evaluator is not much (yet), we would like to put the
user into a world where she uses our environment and our evaluator.
This is essentially a read-eval-print loop which is like the larger
world that drscheme provides for us. So evaluate the following
definitions for the-global-environment and driver-loop.
;;; The driver loop reads an expression, evaluates it in the global
;;; environment, and prints the result. Note that the driver
;;; uses a prompt of "MC-EVAL==> " to help you avoid confusing typing to the
;;; metacircular evaluator with typing to the underlying SCHEME interpreter.
;;; (Also, we have called our evaluator procedures MINI-EVAL and MINI-APPLY
;;; to help you avoid confusing them with Scheme's EVAL and APPLY.)
;;; When/If your interaction with the evaluator bombs out in an error,
;;; you should restart it by calling DRIVER-LOOP again.
;;; make the global environment our little frame for now
(define the-global-environment myframe)
(define (driver-loop)
(newline)
(display "MC-EVAL==> ")
(let ((input (read)))
(if (equal? input '(quit))
"exiting-mini-eval"
(begin
(display (mini-eval input ;;;;change display to user-print later
the-global-environment))
(driver-loop)))))
Recall that myframe is ((x . 17) (cat . 10) (p . 3)).
Now try our mini-micro Scheme interpreter. You
type the bold face stuff.
: (driver-loop)
MC-EVAL==> x
17
MC-EVAL==> cat
10
MC-EVAL==> p
3
MC-EVAL==> (quote (hell0 there))
(hell0 there)
MC-EVAL==> (quit)
"exiting-mini-eval"
If you haven't saved anything yet, now would be a good time to save
this stuff in a week10 subdirectory. I called my code so far:
neval1.scm.
What we have here is a mini-micro Scheme interpreter. Once the user
types (driver-loop), he is in our world. Our interpreter evaluates
whatever expressions are typed by the user until the user quits (or
bombs out by typing something our interpreter cannot handle). We have
a lot of work to do to turn this into a reasonable interpreter, but
this is a start. Make sure you understand how this little interpreter
works and ask for help if you need it.
In order to have a really functional interpreter, we need to add a
lot. Users should be able to define their own variables and give them
values. We should provide some primitive operations that users can
use. Users should be able to define their own functions and invoke
them.
We will begin by working on the environment handling functions so that
users can define their own variables inside our interpreter. Since we
know that eventually we will want to work with environments that
consist of more than one frame, we will define environment functions
that allow for such complex environments.