Chapter 3. Going Further

The preceding chapter prepared you to write Scheme programs using
a small set of the most useful primitive syntactic forms and
procedures.
This chapter introduces a number of additional features and
programming techniques that will allow you to write more sophisticated
and efficient programs.

Section 3.1. Syntactic Extension

As we saw in Section 2.5, the let syntactic form is merely
a syntactic extension defined in terms of a lambda expression and
a procedure application, both core syntactic forms.
At this point, you might be wondering which syntactic forms are core forms
and which are syntactic extensions, and how new syntactic extensions
may be defined. This section provides some answers to both questions.

In truth, it is not necessary for us to draw a distinction between
core forms and syntactic extensions, since once defined, a syntactic
extension has exactly the same status as a core form.
Drawing a distinction, however, makes understanding the language
easier, since it allows us to focus attention on the core forms
and to understand all others in terms of them.

It is necessary for a Scheme implementation to distinguish between core
forms and syntactic extensions.
A Scheme implementation typically expands syntactic extensions
into core forms prior to compilation or interpretation, allowing the
compiler or interpreter to focus only on the core forms.
The set of core forms remaining after expansion to be handled directly
by the compiler or interpreter is implementation-dependent, however,
and may be different from the set of forms described as core here.

The exact set of syntactic forms making up the core of the language
is thus subject to debate, although it must be possible to derive
all other forms from any set of forms declared to be core forms.
The set described here is among the simplest for which this constraint
is satisfied.
It also closely matches the set described as "primitive" in the
ANSI/IEEE Scheme standard and Revised Reports.

The core syntactic forms include top-level define forms,
constants, variables, procedure applications,
quote expressions,
lambda expressions,
if expressions,
and set! expressions.
The grammar below describes the core syntax of Scheme in terms
of these definitions and expressions.
In the grammar, vertical bars ( | ) separate alternatives, and a form
followed by an asterisk ( * ) represents zero or more occurrences
of the form,
<variable> is any Scheme identifier.
<datum> is any Scheme object, such as a number, list, symbol, or
vector.
<boolean> is either #t or #f,
<number> is any number,
<character> is any character, and
<string> is any string.
We have already seen examples of numbers, strings, lists, symbols, and
booleans.
See Chapter 6 or the more detailed grammar at the back
of this book for more on the object-level syntax of these
and other objects.

<program>

<form>*

<form>

<definition> | <expression>

<definition>

<variable definition> | (begin <definition>*)

<variable definition>

(define <variable> <expression>)

<expression>

<constant>

|

<variable>

|

(quote <datum>)

|

(lambda <formals> <expression> <expression>*)

|

(if <expression> <expression> <expression>)

|

(set! <variable> <expression>)

|

<application>

<constant>

<boolean> | <number> | <character> | <string>

<formals>

<variable>

|

(<variable>*)

|

(<variable> <variable>* . <variable>)

<application>

(<expression> <expression>*)

The grammar is ambiguous in that the syntax for procedure applications
conflicts with the syntaxes for quote, lambda,
if, and set! expressions.
In order to qualify as a procedure application, the first <expression> must
not be one of these keywords, unless the keyword has been redefined
or locally bound.

The "defun" syntax for define given in
Section 2.6
is not included in the core, since definitions in that form are
straightforwardly translated into the simpler define syntax.
Similarly, the core syntax for if does not permit the
alternative to be omitted, as did one example in
Section 2.9.
An if expression lacking an alternative can be translated
into the core syntax for if merely by replacing the missing
subexpression with an arbitrary constant, such as #f.

A begin that contains only definitions
is considered to be a
definition in the grammar; this is permitted in order to allow
syntactic extensions to expand into more than one definition.
begin expressions, i.e., begin forms containing expressions,
are not considered core forms.
A begin expression of the form

(begin e1e2 ...)

is equivalent to the lambda application

((lambda () e1e2 ...))

and hence need not be considered core.

Now that we have established
a set of core syntactic forms, let's turn to a discussion of
syntactic extensions.
Syntactic extensions are so called because they extend the syntax of Scheme
beyond the core syntax.
All syntactic extensions in a Scheme program must ultimately
be derived from the core forms.
One syntactic extension, however, may be defined
in terms of another syntactic extension, as long as the latter
is in some sense "closer" to the core syntax.
Syntactic forms may appear anywhere an expression or definition is
expected, as long as the extended form expands into a definition or
expression as appropriate.

Syntactic extensions are defined with define-syntax.
define-syntax is similar to define, except that define-syntax associates
a syntactic transformation procedure, or transformer, with
a keyword (such as let), rather than associating a value with
a variable.
Here is how we might define let with define-syntax:

The identifier appearing after define-syntax is the name, or keyword,
of the syntactic extension being defined, in this case let.
The syntax-rules form is an expression that evaluates to a
transformer.
The item following syntax-rules is a list of
auxiliary keywords and is nearly always ().
An example of an auxiliary keyword is the else of cond.
Definitions requiring the use of auxiliary keywords are given in
Chapter 8.
Following the list of auxiliary keywords is a sequence of one or more
rules, or pattern/template pairs.
Only one rule appears in our definition of let.
The pattern part of a rule specifies the form that the input must take,
and the template specifies to what the input should be transformed.

The pattern should always be a structured expression whose first element
is an underscore ( _ ).
(As we shall see in Chapter 8, the use of _ is only a
convention, but it is a good one to follow.)
If more than one rule is present, the appropriate one
is chosen by matching the patterns, in order, against the input during
expansion.
An error is signaled if none of the patterns match the input.

Identifiers appearing within a pattern are pattern variables,
unless they are listed as auxiliary keywords as described in
Chapter 8.
Pattern variables match any substructure and are bound to that
substructure within the corresponding template.
The notation
pat ... in the pattern allows for zero or more
expressions matching the ellipsis prototype pat in the input.
Similarly, the notation exp ... in the template produces
zero or more expressions from the ellipsis prototype exp in the output.
The number of pats in the input determines the number of exps
in the output; in order for this to work, any ellipsis prototype in the
template must contain at least one pattern variable from an ellipsis
prototype in the pattern.

The single rule in our definition of let should be fairly
self-explanatory, but a few points are worth mentioning.
First, the syntax of let requires that the body contain at least one
expression; hence, we have specified e1 e2 ... instead of
e ..., which might seem more natural.
On the other hand, let does not require that there be at least one
variable/value pair, so we were able to use, simply, (x v) ....
Second, the pattern variables x and v, though together within
the same prototype in the pattern, are separated in the template; any sort
of rearrangement or recombination is possible.
Finally, the three pattern variables x, v, and e2 that
appear in ellipsis prototypes in the pattern also appear in ellipsis
prototypes in the template.
This is not a coincidence; it is a requirement.
In general, if a pattern variable appears within an ellipsis prototype in
the pattern, it cannot appear outside an ellipsis prototype in the template.

The definition of and below is somewhat more complex than the
one for let.

This definition is recursive and involves more than one rule.
Recall that (and) evaluates to #t; the first rule takes care of
this case.
The second and third rules specify the base case and recursion steps
of the recursion and together translate and expressions with
two or more subexpressions into nested if expressions.
For example, (and a b c) expands first into

(if a (and b c) #f)

then

(if a (if b (and c) #f) #f)

and finally

(if a (if b c #f) #f)

With this expansion, if a and b evaluate to a true value, then the value
is the value of c, otherwise #f, as desired.

The definition of or below is similar to the one for and except
that a temporary variable must be introduced for each intermediate
value so that we can both test the value and return it if it is a
true value.
(A similar temporary is not needed for and since there is only one
false value, #f.)

Like variables bound by lambda or let, identifiers introduced
by a template are lexically scoped, i.e., visible only within
expressions introduced by the template.
Thus, even if one of the expressions e2 e3 ... contains a reference
to t, the introduced binding for t does not "capture" those
references.
This is typically accomplished via automatic renaming of introduced
identifiers.

Exercise 3.1.1

Write out the expansion steps necessary to expand

(let ((x (memv 'a ls)))
(and x (memv 'b x)))

into core forms.

Exercise 3.1.2

Write out the expansion steps necessary to expand

(or (memv x '(a b c)) (list x))

into core forms.

Exercise 3.1.3

Look up the description of let* in Chapter 4 and define it
using define-syntax.

Exercise 3.1.4

As we saw in Section 2.9,
it is legal to omit the third, or alternative, subexpression
of an if expression.
Doing so, however, often leads to confusion.
Some Scheme systems provide two syntactic forms,
when and unless, that may be used in place of
such "one-armed" if expressions.

(when testexp1exp2 ...)
(unless testexp1exp2 ...)

With both forms, test is evaluated first.
For when, if test evaluates to true, the remaining
forms are evaluated in sequence as if enclosed in an implicit
begin expression.
If test evaluates to false, the remaining forms are not
evaluated, and the result is unspecified.
unless is similar except that
the remaining forms are evaluated only if test
evaluates to false.

Define when as a syntactic extension in terms of if
and begin, and define unless
in terms of when.

Section 3.2. More Recursion

In Section 2.8, we saw how to define recursive procedures
using top-level definitions.
Before that, we saw how to create local bindings for procedures using
let.
It is natural to wonder whether a let-bound procedure can be
recursive.
The answer is no, at least not in a straightforward way.
If you try to evaluate the expression

you will probably receive an error message to the effect that sum is
undefined.
This is because the variable sum is visible only within the body
of the let expression and not within the lambda expression
whose value is bound to sum.
We can get around this problem by passing the procedure sum to itself
as follows:

This works and is a clever solution, but there is an easier way,
using letrec.
Like let, the
letrec syntactic form includes a set of variable-value pairs,
along with a sequence of expressions referred to as the body of the
letrec.

(letrec ((varval) ...) exp1exp2 ...)

Unlike let, the variables var ... are visible not
only within the body of the letrec but also within
val ....
Thus, we can rewrite the expression above as follows.

In a letrec expression,
val ... are most commonly lambda expressions,
though this need not be the case.
One restriction on the expressions must be obeyed, however.
It must be possible to evaluate each val without
evaluating any of the variables var ....
This restriction is always satisfied if the expressions are all
lambda expressions, since even though the variables may appear
within the lambda expressions, they cannot be evaluated until
the resulting procedures are invoked in the body of the letrec.
The following letrec expression obeys this restriction:

(letrec ((f (lambda () (+ x 2)))
(x 1))
(f)) 3

while the following does not.

(letrec ((y (+ x 2))
(x 1))
y)

The behavior in this case depends upon the implementation.
The expression may return 3, it may return any other value, or it
may result in an error being signaled.

We can use letrec to hide the definitions of "help" procedures
so that they do not clutter the top-level name space.
This is demonstrated by the definition of list? below, which
follows the "hare and tortoise" algorithm
outlined in Exercise 2.9.6.

When a recursive procedure is called in only one place outside
the procedure, as in the example above, it is often clearer to use
a namedlet expression.
Named let expressions take the following form.

(let name ((varval) ...)exp1exp2 ...)

Named let is similar to unnamed let in that it binds
the variables var ... to the values of
val ... within
the body exp1exp2 ....
As with unnamed let, the variables are visible only within the
body and not within val ....
In addition, the variable name is bound within the body to
a procedure that may be called to recur; the arguments to
the procedure become the new values for the variables
var ....

Just as let can be expressed as a simple direct application
of a lambda expression to arguments, named let can be expressed
as the application of a recursive procedure to arguments.
A named let of the form

(let name ((varval) ...)exp1exp2 ...)

can be rewritten in terms of letrec as follows.

((letrec ((name (lambda (var ...) exp1exp2 ...)))name)val ...)

Alternatively, it can be rewritten as

(letrec ((name (lambda (var ...) exp1exp2 ...)))
(nameval ...))

provided that the variable name does not appear free within
val ....

As we discussed in Section 2.8, some recursion is
essentially iteration and executes as such.
When a procedure call is in tail position (see below) with respect to
a lambda
expression, it is considered to be a tail call, and
Scheme systems must treat it properly, as a "goto"
or jump.
When a procedure tail calls itself or calls itself indirectly
through a series of tail calls, the result is tail recursion.
Because tail calls are treated as jumps, tail recursion can be used
for indefinite iteration in place of the more restrictive iteration
constructs provided by other programming languages, without fear of
overflowing any sort of recursion stack.

A call is in tail position with respect to a lambda expression
if its value is returned directly from the lambda expression,
i.e., if nothing is left to do after the call but to return from the
lambda expression.
For example, a call is in tail position if
it is the last expression in the body of a lambda expression,
the consequent or alternative part of an if expression
in tail position,
the last subexpression of an and or or expression in tail
position,
the last expression of a let or letrec in tail position,
etc.
Each of the calls to f in the expressions below
are tail calls, but the calls to g are not.

In each case, the values of the calls to f are returned directly,
whereas the calls to g are not.

Recursion in general and named let in particular provide a natural
way to implement many algorithms, whether iterative, recursive, or
partly iterative and partly recursive; the programmer is not burdened
with two distinct mechanisms.

The following two definitions of factorial use named let
expressions to compute the factorial, n!, of a nonnegative integer n.
The first employs the recursive definition n! = n × (n - 1)!,
where 0! is defined to be 1.

A similar problem is to compute the nth Fibonacci number for a given
n.
The Fibonacci numbers are an infinite sequence of
integers, 0, 1, 1, 2, 3, 5, 8, etc., in which each
number is the sum of the two preceding numbers in the sequence.
A procedure to compute the nth Fibonacci number is most naturally
defined recursively as follows:

This solution requires the computation of the two preceding Fibonacci
numbers at each step and hence is doubly recursive.
For example, to compute (fibonacci 4) requires the computation of both
(fib 3) and (fib 2), to compute (fib 3) requires computing
both (fib 2) and (fib 1), and to compute (fib 2) requires
computing both (fib 1) and (fib 0).
This is very inefficient, and it becomes more inefficient as n grows.
A more efficient solution is to adapt the accumulator solution of the
factorial example above to use two accumulators, a1 for the
current Fibonacci number and a2 for the preceding one.

Here, zero is treated as a special case, since there is no preceding value.
This allows us to use the single base case (= i 1).
The time it takes to compute the nth Fibonacci number using this
iterative solution grows linearly with n, which makes a
significant difference when compared to the doubly recursive version.
To get a feel for the difference, try computing (fibonacci 30) and
(fibonacci 35) using
both definitions to see how long each takes.

We can also get a feel for the difference by looking at a trace for
each on small inputs.
The first trace below shows the calls to fib in the non-tail-recursive
version of fibonacci, with input 5.

Notice how there are several calls to fib with arguments 2, 1, and 0.
The second trace shows the calls to fib in the tail-recursive version,
again with input 5.

|(fib 5 1 0)
|(fib 4 1 1)
|(fib 3 2 1)
|(fib 2 3 2)
|(fib 1 5 3)
|5

Clearly, there is quite a difference.

The named let examples shown so far are either tail-recursive or not
tail-recursive.
It often happens that one recursive call within the same expression is
tail-recursive while another is not.
The definition of factor below computes the prime factors of its
nonnegative integer argument; the first call to f is not
tail-recursive, but the second one is.

A nontail call to f is shown indented relative to its caller,
since the caller is still active, whereas tail calls appear at
the same level of indentation.

Exercise 3.2.1

Which of the procedures defined in Section 3.2 are
tail-recursive, and which are not?

Exercise 3.2.2

Rewrite factor using letrec to bind f in place of named let.

Exercise 3.2.3

Can the letrec expression defining even? and odd?
on page 54
be rewritten using named let?
If not, why not?
If so, do it.

Exercise 3.2.4

Rewrite both definitions of fibonacci given in this section to count
the number of recursive calls to fib, using a counter similar to the
one used in the cons-count example of Section 2.9.
Count the number of recursive calls made in each case for several
input values.
What do you notice?

Exercise 3.2.5

Augment the definition of let given in Section 3.1 to
handle named let as well as unnamed let, using two rules.

Exercise 3.2.6

The following definition of or is simpler than the one given
in Section 3.1.

Say why it is not correct.
[Hint: Think about what would happen if this version of or
were used in the even? and odd? example given in this section, for
very large inputs.]

Exercise 3.2.7

The definition of factor is not the most efficient possible.
First, no factors of n besides n itself can possibly be found
beyond .
Second, the division (/ n i) is performed twice when a factor is
found.
Third, after 2, no even factors can possibly be found.
Recode factor to correct all three problems.
Which is the most important problem to solve?
Are there any additional improvements you can make?

Section 3.3. Continuations

During the evaluation of a Scheme expression, the
implementation must keep track of two things:
(1) what to evaluate and (2) what to do with the value.
Consider the evaluation of (null? x) within the expression
below.

(if (null? x) (quote ()) (cdr x))

The implementation must first evaluate (null? x) and, based on its value,
evaluate either (quote ()) or (cdr x).
"What to evaluate" is (null? x), and "what to do with the value"
is to make the decision which of (quote ()) and (cdr x) to
evaluate and do so.
We call "what to do with the value" the continuation of a
computation.

Thus, at any point during the evaluation of any expression, there is a
continuation ready to complete, or at least continue, the computation
from that point.
Let's assume that x has the value (a b c).
Then we can isolate six continuations during the evaluation of
(if (null? x) (quote ()) (cdr x)), the continuations waiting for:

the value of (if (null? x) (quote ()) (cdr x)),

the value of (null? x),

the value of null?,

the value of x,

the value of cdr, and

the value of x (again).

The continuation of (cdr x) is not listed because it is the same
as the one waiting for (if (null? x) (quote ()) (cdr x)).

Scheme allows the continuation of any expression to be obtained with
the procedure
call-with-current-continuation, which may be abbreviated
call/cc in most implementations.
(We use the shorter name here.
If the implementation you are using does not recognize call/cc,
simply define it to be call-with-current-continuation or use
the longer name in your code.)

call/cc must be passed a procedure p of
one argument.
call/cc obtains the current continuation and passes it to p.
The continuation itself is represented by a procedure k.
Each time k is applied to a value, it returns
the value to the continuation of the call/cc application.
This value becomes, in essence, the value of the application of
call/cc.

If p returns without invoking k,
the value returned by the procedure becomes the value of the
application of call/cc.

Consider the simple examples below.

(call/cc
(lambda (k)
(* 5 4))) 20

(call/cc
(lambda (k)
(* 5 (k 4)))) 4

(+ 2
(call/cc
(lambda (k)
(* 5 (k 4))))) 6

In the first example, the continuation is obtained and bound to k,
but k is never used, so the value is simply the product
of 5 and 4.
In the second, the continuation is invoked before the multiplication, so
the value is the value passed to the continuation, 4.
In the third, the continuation includes the addition by 2; thus, the
value is the value passed to the continuation, 4, plus 2.

Here is a less trivial example, showing the use of
call/cc to provide a nonlocal exit from a recursion.

The nonlocal exit allows product to return immediately,
without performing the pending multiplications,
when a zero value is detected.

Each of the continuation invocations above returns to the continuation
while control remains within the procedure passed to call/cc.
The following example uses the continuation after this procedure
has already returned.

(let ((x (call/cc (lambda (k) k))))
(x (lambda (ignore) "hi"))) "hi"

The continuation obtained by this invocation of call/cc may be described as
"Take the value, bind it to x, and apply the value of x to
the value of (lambda (ignore) "hi")."
Since (lambda (k) k) returns its argument, x is bound to the
continuation itself; this continuation is applied to the procedure resulting
from the evaluation of (lambda (ignore) "hi").
This has the effect of binding x (again!) to this procedure and
applying the procedure to itself.
The procedure ignores its argument and returns "hi".

The following variation of the example above is probably the most confusing
Scheme program of its size; it may be easy to guess what it returns,
but it takes some work to verify that guess.

(((call/cc (lambda (k) k)) (lambda (x) x)) "HEY!") "HEY!"

The value of the call/cc is its own continuation, as in the preceding
example.
This is applied to the identity procedure (lambda (x) x), so the
call/cc returns a second time with this value.
Then, the identity procedure is applied to itself, yielding the identity
procedure.
This is finally applied to "HEY!", yielding "HEY!".

Continuations used in this manner are not always so puzzling.
Consider the following definition of factorial that saves the
continuation at the base of the recursion before returning 1, by
assigning the top-level variable retry.

With this definition, factorial works as we expect
factorial to work, except it has the side effect of assigning
retry.

(factorial 4) 24
(retry 1) 24

The continuation bound to retry might be described as "Multiply
the value by 1, then multiply this result by 2, then multiply
this result by 3, then multiply this result by 4."
If we pass the continuation a different value, i.e., not 1, we will
cause the base value to be something other than 1 and hence change
the end result.

(retry 2) 48
(retry 5) 120

This mechanism could be the basis for a breakpoint package implemented with
call/cc; each time a breakpoint is encountered, the continuation of
the breakpoint is saved so that the computation may be restarted from the
breakpoint (more than once, if desired).

Continuations may be used to implement various forms of multitasking.
The simple "light-weight process" mechanism defined below allows
multiple computations to be interleaved.
Since it is nonpreemptive, it requires that each process voluntarily
"pause" from time to time in order to allow the others to run.

See Section 9.11 for an implementation of engines,
which support preemptive multitasking, with call/cc.

Exercise 3.3.1

Use call/cc to write a program that loops indefinitely, printing
a sequence of numbers beginning at zero.

Exercise 3.3.2

Rewrite product without call/cc, retaining the feature that no
multiplications are performed if any of the list elements are zero.

Exercise 3.3.3

What would happen if a process created by lwp as defined above
were to terminate, i.e., simply return without calling
pause?
Define a quit procedure that allows a process to terminate
properly.
Be sure to handle the case in which the only remaining process
terminates.

Exercise 3.3.4

Each time lwp is called, the list of processes is copied
because lwp uses append to add its argument to the
end of the process list.
Design and implement a queue abstraction that does not suffer
from this problem and use it in the implementation of
light-weight processes.

Exercise 3.3.5

The light-weight process mechanism allows new processes to be created
dynamically, although the example given in this section does not
do so.
Design an application that requires new processes to be created
dynamically and implement it using the light-weight process mechanism.

Section 3.4. Continuation Passing Style

As we discussed in the preceding section, a continuation
waits for the value of each expression.
In particular, a continuation is associated with each procedure call.
When one procedure invokes another via a nontail call, the called
procedure receives an implicit continuation that is responsible for
completing what is left of the calling procedure's body plus returning
to the calling procedure's continuation.
If the call is a tail call, the called procedure simply receives
the continuation of the calling procedure.

We can make the continuations explicit by encapsulating "what to do"
in an explicit procedural argument passed along on each call.
For example, the continuation of the call to f in

conses the symbol b onto the value returned to it, then returns
the result of this cons to the continuation of the call to g.
This continuation is the same as the continuation of the call to
h, which conses the symbol d onto the value returned to
it.
We can rewrite this in continuation-passing style, or CPS,
by replacing these implicit continuations with explicit procedures.

Like the implicit continuation of h and g in the preceding
example, the explicit continuation passed to h and on to g,

(lambda (v) (cons 'd v))

conses the symbol d onto the value passed to it.
Similarly,
the continuation passed to f,

(lambda (v) (k (cons 'b v)))

conses b onto the value passed to it, then passes this on to the
continuation of g.

Expressions written in CPS are more complicated, of course, but
this style of programming has some useful applications.
CPS allows
a procedure to pass more than one result to its continuation,
because the procedure that implements the continuation can
take any number of arguments.

(This can be done with multiple values as well; see Section 5.8.)
CPS also allows a procedure to take separate "success" and
"failure" continuations, which may accept different numbers of arguments.
An example is integer-divide below, which passes the quotient
and remainder of its first two arguments to its third, unless the
second argument (the divisor) is zero, in which case it passes an
error message to its fourth argument.

The procedure quotient, employed by integer-divide,
returns the quotient of its two arguments,
truncated towards zero.

Explicit success and failure continuations can sometimes
help to avoid the extra communication necessary to separate successful
execution of a procedure from unsuccessful execution.
Furthermore, it is possible to have multiple success or failure
continuations for different flavors of success or failure, each
possibly taking different numbers and types of arguments.
See Sections 9.10 and 9.11 for extended examples
that employ continuation-passing style.

At this point you may be wondering about the relationship between
CPS and the continuations obtained via call/cc.
It turns out that any program that uses call/cc can be rewritten
in CPS without call/cc, but a total rewrite of the program
(sometimes including even system-defined primitives) may be
necessary.
Try to convert the product example on
page 61
into CPS before looking at the version below.

In fact, internal definitions and letrec are practically interchangeable.
It should not be surprising, therefore, that a lambda, let, or
letrec body containing internal definitions can be replaced with
an equivalent letrec expression.
A body of the form

(define varval)exp1exp2

is equivalent to a letrec
expression binding the defined variables to the associated values
in a body comprising the expressions.

(letrec ((varval) ...) exp1exp2 ...)

Conversely, a letrec of the form

(letrec ((varval) ...) exp1exp2 ...)

can be replaced with a let expression containing internal definitions
and the expressions from the body as follows.

(let ()
(define varval)exp1exp2
)

The seeming lack of symmetry between these transformations is due to
the fact that letrec expressions can appear anywhere an expression
is valid, whereas internal definitions can appear only at the front
of a body.
Thus, in replacing a letrec with internal definitions, we must generally
introduce a let expression to hold the definitions.

Syntax definitions may also appear at the front of a lambda, let,
or letrec body.

The scope of a syntactic extension established by an internal syntax
definition, as with an internal variable definition, is limited to
the body in which the syntax definition appears.

Internal definitions may be used in conjunction with top-level
definitions and assignments to help modularize programs.
Each module of a program should make visible only those bindings
that are needed by other modules, while hiding other bindings that
would otherwise clutter the top-level namespace and possibly
result in unintended use or redefinition of those bindings.
A common way of structuring a module is shown below.

The first set of definitions establish top-level bindings for the
variables we desire to export (make visible globally).
The second set of definitions establish local bindings visible only
within the module.
The expressions init-exp ... perform any initialization that
must occur after the local bindings have been established.
Finally, the set! expressions assign the exported variables to
the appropriate values.
Some of the extended examples in Chapter 9
use this modularization technique.

One advantage of this form of modularization is that the bracketing
let expression may be removed or "commented out" during
program development, making the internal definitions top-level to
facilitate interactive testing.

The following module exports a single variable, calc, which is
bound to a procedure that implements a simple four-function
calculator.

This example uses a case expression to determine which operator
to apply.
case is similar to cond except that the test is always the
same: (memv val (key ...)), where val
is the value of the first case subform and (key ...)
is the list of items at the front of each case clause.
The case expression in the example above could be rewritten
using cond as follows.

Exercise 3.5.1

Replace complain in the calc example with an equivalent syntactic
extension.

Exercise 3.5.2

In the calc example, the error continuation ek is passed
along on
each call to apply-op, complain, and do-calc.
Move the definitions of apply-op, complain, and
do-calc to within the scope of the binding for ek established
within calc.
Then eliminate the ek argument from the definitions and applications
of apply-op, complain, and do-calc.

Exercise 3.5.3

Determine the error-signaling facilities provided by the Scheme
implementation you are using.
Eliminate the call/cc from calc and rewrite complain to
signal an error.