CSU660 Scheme Evaluation Rules

Formal rules for evaluating Scheme Using substitution.



Preliminaries

These are rules for evaluating Scheme using the substitution model. There are separate rules for every kind of Scheme expression we want to handle.

There are certain notational conventions to be aware of:

Note that in DrScheme you can switch to one of the “Student” languages and use the stepper to see these rules in action.

The rules follow, divided into groups.



Basic evaluation

These rules cover basic Scheme forms that are not special forms.

Evalprim
Primitive value expressions have their obvious values. To evaluate such an expression, you simply take its value. These include: (Usually primitive values are obvious, so this rule is used implicitly.)

Evalvar
Identifiers denote bindings and are implemented as symbols. To evaluate an expression x which is an identifier, you return this identifier's value which should be a value that you know by the Evaldef rule below. Note that (a) this does not involve further evaluation, and (b) it applies only for global identifiers since local ones are substituted away.

Evalap
To evaluate a compound form (E1 E2 ... En), which is not a special form (see below), you evaluate each of its sub-expressions recursively, getting a list of values (v1 v2 ... vn). Then continue with one of the Apply rules to apply the function v1 on the arguments v2, ...vn. This rule is the default that is used only when the expression is not one of the special form cases,so its place should be at the end of the Eval rules, but it is here for clarity.


Application rules

These are the two application rules. Note that these rules deal with evaluated inputs.

Applyprim
To apply a primitive function f on arguments v1, v2, ..., vn, you simply compute the function result. This uses knowledge about the function which is taken as built-in — it is not part of these rules.
Example:
(+ 3 4) Applyprim⇒ 7
(car (cons v1 v2)) Applyprim+Applyprim⇒ v1 ; after two usages

Applylam
To apply a lambda expression (lambda (x1...xn) E) on some given value arguments v1, v2, ..., vn, you substitute the values for the argument names in the body of the lambda expression: E[v1, v2, ..., vn/x1, x2, ..., xn]. In other words, you substitute every free occurrence of xi in E by the corresponding vi value.
You then (recursively) continue the evaluation process with the result.
Examples:
((lambda (x y) (+ x y)) 3 4) Applylam⇒ (+ 3 4) Applyprim⇒ 7
((lambda (x) (lambda (y) (+ x y))) 3) Applylam⇒ (lambda (y) (+ 3 y))


Special forms

The rest are special-form rules. Here we have only two basic rules, and then later a few rules for derived expressions.

Evaldef
To evaluate a simple define expression (define x E) where x is an identifier, you evaluate E recursively to get a value v, and you mark to yourself that now you now know that the value associated with the identifier x is v. By “know” we mean some way that can be used in the Evalvar rules above. Important: this rule can only be applied to top-level expressions.

Evalif
To evaluate an if form (if E1 E2 E3), you recursively evaluate E1 to get a value v1. If v1 is true (anything other than #f), then continue by evaluating (and returning the value of) E2, and if v1 is #f, then continue with E3.


Derived forms

Finally, these forms are all derived forms, they are evaluated by rewrite rules which transform them into a different form (eventually to a form that uses only primitives).

Evalfdef
To evaluate a define expression that is a syntactic sugar for a function definition, (define (x1 x2 ... xn) E), you continue by evaluating the form (define x1 (lambda (x2 ... xn) E)).

Evallet
A let form is evaluated by this rewrite rule:
(let ([x1 E1] ... [xn En]) E)((lambda (x1 ... xn) E) E1 ... En)
Essentially, what you get is to evaluate E with xi bound to the result of evaluating Ei. For this reason, it is ok to skip the rewritten form and use this rule together with the Applylam rule. For example, instead of writing:
(let ([x 1] [y 2]) (+ x y)) Evallet⇒ ((lambda (x y) (+ x y)) 1 2) Applylam⇒ (+ 1 2)
you can write:
(let ([x 1] [y 2]) (+ x y)) Evallet+Applylam⇒ (+ 1 2)
But keep in mind that in both of these cases you should evaluate the expressions 1 and 2 before you do the substitutions. Also, note that the translation step makes things more verbose but using it makes it harder to make a mistakes, for example, it is clear that if you replace the 2 in the original expression by x, then this x is a free (unbound) identifier.

Evallet*
This rewrite rule has two cases for a let* form with either zero or more bindings: Again, you can use your intuition here, and bind the names, keeping in mind that you do it one by one — and you can combine this rewrite with the Evallet and even combine that further with the Applylam rule. For example, instead of:
(let* ([x 1] [y x]) (+ x y)) Evallet*⇒ (let ([x 1]) (let* ([y x]) (+ x y))) Evallet⇒ ((lambda (x) (let* ([y x]) (+ x y))) 1) Applylam⇒ (let* ([y 1]) (+ 1 y)) Evallet*⇒ (let ([y 1]) (let* () (+ 1 y))) Evallet⇒ ((lambda (y) (let* () (+ 1 y))) 1) Applylam⇒ (let* () (+ 1 1)) Evallet*⇒ (let () (+ 1 1)) Evallet⇒ ((lambda () (+ 1 1))) Applylam⇒ (+ 1 1) Applyprim⇒ 2
you can write:
(let* ([x 1] [y x]) (+ x y)) Evallet*+Evallet+Applylam⇒ (let* ([y 1]) (+ 1 y)) Evallet*+Evallet+Applylam⇒ (let* () (+ 1 1)) Evallet*+Evallet+Applylam⇒ (+ 1 1) Applyprim⇒ 2
The same notes as above hold here.

Evalcond
Again, a rewrite rule with several cases: Note that this makes cond return false when no conditions match and no else clause is given — this is different from “real” Scheme, but we use this due to the lack of undefined values and errors (our if form must have two sides).

Evaland

Evalor
Note the need for a let in the last case.