(This section could be skimmed if you're not interested in the read-eval-print-loop, which is just a simple command interpreter that acts as a "front end" to the evaluator.)
When you're interacting with Scheme by typing text, you're interacting with a Scheme procedure called the read-eval-print loop. This procedure just loops, accepting one command at a time, executing it, and printing the result.
The three steps at each iteration of the loop are:
readto read the characters that make up a textual expression expression from the keyboard input buffer, and construct a data structure to represent it,
evalto evaluate the expression--intuitively,
eval"figures out what the expression means," and "does what it says to do," returning the value of the expression--and
writeto print a textual representation of the resulting from
eval, so that the user can see it.
(More generally, we might read expressions from a file rather than the keyboard buffer. We'll ignore that for now.)
You can write your own read-eval-print loop for your own programs,
so that users can type in expressions, and you can interpret them
any way you want. Later, I'll show how to write an evaluator,
and this will come in handy. You can start up your read-eval-print
loop (by typing in
(rep-loop)), and it will take over from the
normal Scheme read-eval-print loop, interpreting expressions your
Here's a very simple read-eval-print loop:
(define (rep-loop) (display "repl>") ; print a prompt (write (eval (read))) ; read expr., pass to eval, write result (rep-loop)) ; loop (tail-recursive call) to do it again
(Notice that the expression
(write (eval (read))) does
things in the proper read-eval-print order, because the argument
to each procedure call is computed before the actual call. In
Scheme, as in most languages, nested procedure calls expressions
are done "from the inside out.")
I've coded the iteration recursively, rather than using a looping construct. The procedure is tail-recursive, since all it does at the end is call itself. Remember that Scheme is smart about this kind of recursion, and won't build up procedure activation information on the stack and cause a stack overflow. You can do tail recursion all day. Since nothing happens in a given call to the procedure after the tail-call, Scheme can avoid returning to it at all, and avoid saving any state to return to.
The above read-eval-print loop isn't very friendly, because it loops
infinitely without giving you any chance to break out of it. Let's
modify it to allow you to stop the tail recursion by typing in the
(define (rep-loop) (display "repl>") ; print a prompt (let ((expr (read))) ; read an expression, save it in expr (cond ((eq? expr 'halt) ; user asked to stop? (display "exiting read-eval-print loop") (newline)) (#t ; otherwise, (write (eval expr)) ; evaluate and print (newline) (rep-loop))))) ; and loop to do it again
Notice that this is still tail recursive, because the branch that does the recursive call doesn't do anything else after that.
This read-eval-print loop could be improved a little. By using the
halt as the command to tell the loop to stop, we prevent
people from being able to evaluate
halt as an expression. We
could get around this by ensuring that the halt command doesn't have
the syntax of any expression in the language, but we won't bother right now.
Another improvement would be to make it possible to use different
interpreters with the same read-eval-print loop. The
procedure above assumes that it should call a procedure named
to evaluate an expression. We'd like to write a
works with different evaluators, so instead of having it call
by name, we'll hand it an argument saying which evaluator to use.
Since procedures are first class, we can just hand it a pointer to
the evaluation procedure.
(define (rep-loop evaluator) (display "repl>") ; print a prompt (let ((expr (read))) ; read an expression, save it in expr (cond ((eq? expr 'exit) ; user asked to stop? (display "exiting read-eval-print loop") (newline)) (#t ; otherwise, (write (evaluator expr)) ; evaluate and print (rep-loop evaluator))))) ; and loop to do it again
Here I just made three changes. I added an argument
which is expected to be a procedure. Then we changed the call to
eval to call
our-eval, i.e., whatever evaluator was
given. Then we changed the recursive call to
rep-loop to pass
that argument on to the next recursive call.