All the Evaluators and What They're For

I received a request for a review sheet for all the different kinds of evaluators we've worked with. In general, each of them represents a different variation of Scheme: providing some new feature that normal Scheme doesn't support. Let's get a quick overview of all the evaluators we've covered, then examine each of them in a bit more detail.

If you take nothing else away, though, it's to remember that the evaluator is stupid. Don't try to be smart and anticipate what any of them are going to do. Just follow the rules very carefully, and you should be all right. And remember, if an evaluator doesn't explicitly change how something is done, it should be the same as in regular Scheme!

Evaluator Above-the-Line Changes Below-the-Line Changes
Metacircular Evaluator Missing a few Scheme features, like vectors and some special forms. (reference implementation)
Analyzing Evaluator None Analyzes code, including procedure bodies, when it is typed in.
A form of compilation.
Lazy Evaluator Normal-order evaluation
(for compound procedure calls)
Arguments to compound procedures
are delayed; these promises are
forced only when necessary.
Logo Evaluator
(Project 4)
Speaks Logo instead of Scheme,
has dynamic scope.
Different evaluation model, procedures stored separately from variables.

Metacircular Evaluator

There's not much to say about the metacircular evaluator: it reads, evaluates, prints, and repeats (loops). mc-eval contains a big cond that decides what to do with a given expression, based on what kind of expression it is. Procedure calls are handled by mc-apply, which differentiates between primitive calls (which use the underlying Scheme / STk apply) and compound calls (which are handled within the MCE code). All special forms are checked for and handled "manually", meaning we get little help from the underlying Scheme.

Analyzing Evaluator

The analyzing evaluator is, from the user's point of view, exactly the same as the "vanilla" unmodified MCE. The difference is in how expressions are handled. Instead of reading each expression and then doing what it says, the analyzing evaluator reads all expressions in advance, then builds a machine (a procedure in the underlying Scheme) that will evaluate the expressions in a given environment. It's like the difference between solving a math problem and writing a program to solve it for you—the first one might be faster for just one problem, but if you have to solve many problems the time spent writing the program will quickly become worth it.

In general, we discount the analyzing evaluator's overhead in "compiling" code that is only run once; while the MCE is technically faster when you just evaluate something once, it's not a very significant difference. The big win for the analyzing evaluator comes when a compound procedure is called over and over. Because the body of the procedure has already been analyzed, there's no need to figure out what to do every time. You just feed in a new environment and get your answer out.

To summarize, if a procedure is called more than once, the analyzing evaluator will be more efficient than the vanilla MCE. If it is only called once, then they both have to "analyze" once, and so the MCE wins on the overhead technicality.

Lazy Evaluator

Now we're coming to something new! The lazy evaluator swaps Scheme's familiar applicative-order evaluation for exotic normal-order evaluation. How does it do this? By delaying every expression until it is absolutely needed. And yes, by delaying we mean promises, though as usual we don't use STk's implementation.

There are a few simple rules to the lazy evaluator:

  1. Delay all arguments to a compound procedure call.
  2. Force all arguments to a primitive procedure call.
  3. Force the condition of an if, so you can actually test it.
  4. Force a procedure being called, so you can actually call it.
  5. Force a value returned to the top level REPL, so it can be printed as a value and not a promise.

Everything else is the same as the vanilla MCE. Of course, as we've seen a lot, side effects tend to get lost or a little messed up in normal-order evaluation. In a real normal-order system, you should stick to functional programming.

Logo Evaluator

The most important differences (besides the syntax, particularly the infix notation) is that Logo uses dynamic scope, and has first-class expressions, rather than first-class procedures. That's how we can have ifelse not be a special form in Logo; there are no special forms except to.

? ifelse 2=3 [print "yes] [print "no]
no
? make first "abc "hi
? :a
hi

You can't do that call to make in Scheme at all, because define (and set!) are special forms!

In addition, Logo allows you to have a procedure and a variable with the same name, and they won't overlap.

? make "first [brian harvey]
? pr first :first
brian

Hopefully that was helpful! You should be able to keep all the evaluators straight, and understand what each one adds to the Scheme language. Now go ace this final!

Back to Jordy's home page | Course home page