Chapter 14
The Built-In Macros and Special Definitions
Statements
Statements are used to implement a variety of program constructs.
Many statements include an optional implicit body, which may contain one or more
constituents separated by semicolons. When an implicit body is executed, the expressions in
the implicit body are executed in order (left to right). The values of the implicit body are
the values of the last expression. If the optional implicit body is not present or contains
no expressions, the return value is #f
.
Macro |
Description |
Page |
---|---|---|
|
Executes an implicit body if the value of a test is true or an alternate if the test is false. |
|
|
Executes an implicit body unless the value of a test is true. |
|
|
||
|
||
|
Repeatedly executes a body until a test expression is false. |
|
|
||
|
||
|
||
|
Executes a body with several options for nonstandard flow of control. |
|
|
Conditionals
The following statements are used to perform conditional execution.
if
[Statement]
Executes an implicit body if the value of a test is true or an alternate if the test is false.
- Macro Call:
-
if (
test)
[ consequent ]
{elseif
(
elseif-test)
[ elseif-consequent ] }*
[else
[ alternate ] ]
end
[if
] - Arguments:
-
- test
expressionbnf
- consequent
bodybnf
- elseif-test
expressionbnf
- elseif-consequent
bodybnf
- alternate
bodybnf
- Values:
Zero or more instances of
<object>
.- Description:
-
if
executes one or more expressions, executing and returning the values of a body following the first test that returns true.test is the first expression to be executed. If its value is true,
if
executes and returns the values of the consequent. If the value of test is false,if
proceeds with the optional elseif-tests and alternate.First the elseif clauses are tried in order. The first elseif-test is executed. If its value is true, the corresponding elseif-consequent is executed and its values are returned as the value of the
if
statement. If its value is false, the next elseif-test is tried. This continues until a true elseif-test is found, or until there are no more elseif clauses.If the test and all the elseif-tests are false, the alternate is executed and its values are returned as the value of the
if
statement. If there is no alternate, theif
statement returns#f
.if ( x < 0 ) - x; end if; if ( heads?(flip(coin)) ) start(black); else start(white); end if if (player1.money <= 0) end-game(player1) elseif (player2.money <= 0) end-game(player2) else move(player1); move(player2); end if if ( camel.humps = 1 ) "dromedary" elseif ( camel.humps = 2 ) "bactrian" else "not a camel" end if;
unless
[Statement]
Executes an implicit body unless the value of a test is true.
- Macro Call:
unless (
test)
[ body ]
end
[unless
]- Arguments:
-
- test
expressionbnf
- body
bodybnf
- Values:
Zero or more instances of
<object>
.- Description:
-
unless
executes test. If the value of test is false, then the body is executed and its values are returned byunless
. If the value of test is true, the body is not executed andunless
returns#f
.If there are no expressions in the body, then
#f
is returned.unless(detect-gas? (nose)) light(match) end unless
case
[Statement]
- Macro Call:
-
case
{ test=>
consequent }*
[otherwise
[=>
] alternate ]
end
[case
] - Arguments:
-
- test
expressionbnf
- consequent
[ constituentsbnf ]
;
- alternate
[ constituentsbnf ]
;
- Values:
Zero or more instances of
<object>
.- Description:
-
case
executes the test in order, until it reaches a test that returns true. When it reaches a test that returns true, it executes the corresponding consequent and returns its values. Subsequent tests are not executed. If the corresponding consequent is empty, the first value of the successful test is returned.As a special case, the name
otherwise
may appear as a test. This test always succeeds if there is no preceding successful test.If no test is true, then
case
returns#f
.case player1.money <= 0 => end-game(player1); player2.money <= 0 => end-game(player2); otherwise => move(player1); move(player2); end case;
select
[Statement]
- Macro Call:
-
select (
target [by
test ])
{ matches=>
consequent }*
[otherwise
[=>
] alternate ]
end
[select
] - Arguments:
-
- target
expressionbnf
- test
expressionbnf
- matches
{ expressionbnf }
,
+ |(
{ expressionbnf },
+)
- consequent
[ constituentsbnf ]
;
- alternate
[ constituentsbnf ]
;
- Values:
Zero or more instances of
<object>
.- Description:
-
select
generates a target object and then compares it to a series of potential matches, in order. If it finds a match, it executes the corresponding consequent and returns the values of the consequent. If no match is found, an error is signaled.The target is executed to produce the match object.
The test, if supplied, is a function used to compare the target object to the potential matches. The default test is
==
.One at a time, each match is executed and its value compared to target, in order. If a match is found, the corresponding consequent is executed and its values are returned. If the corresponding consequent is empty,
#f
is returned.Once a match is found, subsequent matches and the corresponding bodies are not executed.
As a special case, the name
otherwise
may appear instead of a matches. This will be considered a match if no other match is found.If there is no matching clause, an error is signaled. Because an
otherwise
clause matches when no other clause matches, aselect
form that includes anotherwise
clause will never signal an error for failure to match.Since testing stops when the first match is found, it is irrelevant whether the test function would also have returned true if called on later matches of the same clause or on matches of later clauses.
select ( career-choice(student) ) art:, music:, drama: => "Don't quit your day job"; literature:, history:, linguistics: => "That really is fascinating"; science:, math:, engineering: => "Say, can you fix my VCR?"; otherwise => "I wish you luck"; end select; select ( my-object by instance? ) <window>, <view>, <rectangle> => "a graphical object"; <number>, <string>, <list> => "a computational object"; otherwise => "I don't know"; end select
Iteration Constructs
while
[Statement]
Repeatedly executes a body until a test expression is false.
- Macro Call:
while (
test)
[ body ]
end
[while
]
⇒#f
- Arguments:
-
- test
expressionbnf
- body
bodybnf
- Values:
#f
- Description:
-
while
loops over body until test returns false.Each pass through the loop begins by executing test. If test returns a true value, the expressions in the body are executed and the looping continues. If test returns false, the loop terminates and
while
returns#f
.
until
[Statement]
Repeatedly executes a body until a test expression is true.
- Macro Call:
-
until (
test)
[ body ]
end
[until
]
⇒#f
- Arguments:
-
- test
expressionbnf
- body
bodybnf
- Values:
#f
- Description:
-
until
loops over body until test returns true.Each pass through the loop begins by executing test. If test returns false, the expressions in the body are executed and the looping continues. If test returns true, the loop terminates and
until
returns#f
.
for
[Statement]
- Macro Call:
-
for (
{ for-clause },
* |
,
}* end-clause })
[ loop-body ]
[finally
[ result-body ] ]
end
[for
] - Arguments:
-
- for-clause
explicit-step-clause |
collection-clause |
numeric-clause- end-test
expressionbnf
- loop-body
bodybnf
- result-body
bodybnf
- explicit-step-clause
variablebnf
=
init-valuethen
next-value- collection-clause
variablebnf
in
collection- numeric-clause
-
variablebnf
from
start
[ {to
|above
|below
} bound ]
[by
increment ] - end-clause
{
until:
|while:
} end-test- init-value
expressionbnf
- next-value
expressionbnf
- collection
expressionbnf
- start
expressionbnf
- bound
expressionbnf
- increment
expressionbnf
- Values:
Zero or more instances of
<object>
.- Description:
-
for
iterates over loop-body, creating and updating iteration bindings on each iteration according to the for-clauses. Iteration ends when one of the for-clauses is exhausted, or when the optional end-test is satisfied.Each for-clause controls one iteration binding. The optional end-test does not control any iteration bindings.
There are three kinds of for-clauses: explicit-step-clauses, collection-clauses, and numeric-clauses: An explicit-step-clause creates bindings for the results of executing an expression. A collection-clause creates bindings for successive elements of a collection. A numeric-clause creates bindings for a series of numbers.
Execution of a
for
statement proceeds through the following steps:- Execute the expressions that are executed just once, in left to right order as they
appear in the
for
statement. These expressions include the types of all the bindings, and the expressions init-value, collection, start, bound, and increment. If the value of collection is not a collection, an error is signaled. The default value for increment is1
. - Create the iteration bindings of explicit step and numeric clauses.
- For each explicit step clause, create the binding for the value of init-value. If the binding is typed and the value is not of the specified type, signal an error.
- For each numeric clause, create the binding for the value of start. If the binding is typed and the value is not of the specified type, signal an error.
- Check numeric and collection clauses for exhaustion. If a clause is exhausted, go to
step 9.
- A collection clause is exhausted if its collection has no next element.
- A numeric clause is exhausted if a bound is supplied and the value of
the clause is no longer in bounds. If
above
is specified, the clause will be in bounds as long as the value is greater than the bounds. Ifbelow
is specified, the clause will be in bounds as long as the value is less than the bounds. Ifto
is specified with a positive or zero increment, the clause will be in bounds as long as it is less than or equal to the bounds. Ifto
is specified with a negative increment, the clause will be in bounds as long as it is greater than or equal to the bounds.
- For each collection clause create the iteration binding for the next element of the collection for that clause. Fresh bindings are created each time through the loop (i.e., the binding is not assigned the new value). If the binding is typed and the value is not of the specified type, signal an error.
- If end-test is supplied, execute it. If the value
of end-test is false and the symbol is
while:
, go to step 9. If the value of end-test is true and the symbol isuntil:
, go to step 9. - Execute the expressions in the body in order. The expressions in the body are used to produce side-effects.
- Obtain the next values for explicit step and numeric clauses. Values are obtained in
left to right order, in the environment produced by step 6.
- For each explicit step clause, execute next-value.
- For each numeric clause, add the increment to the current value of
the binding, using
+
.
- Create the iteration bindings of explicit step and numeric clauses for the values obtained in step 7. For each clause, if a binding type is supplied and the next value for that clause is not of the specified type, signal an error. Fresh bindings are created each time through the loop (i.e., the binding is not assigned the new value). After the bindings have been created, go to step 3.
- Execute the expressions in the result-body in order. Bindings created in
step 2 and 8 are visible during the execution of result-body, but bindings
created in step 4 ( the iteration bindings of collection clauses) are not visible
during the execution of result-body. The values of the last expression in
the result-body are returned as the values of the
for
statement. If there are no expressions in the result-body,for
returns#f
.
for ( thing = first-thing then next(thing), until: done?(thing) ) do-some(thing) end; for (j :: <integer> from 0 to height) for (i :: <integer> from 0 to width) erase(i,j); plot (i,j); end for; end for; for (city in olympic-cities, year from start-year by 4) schedule-olympic-game(city, year) finally notify(press); sell(tickets); end; for (i from 0 below 100, zombies from 0 below 100, normals from 100 above 0 by -1) population[i] := zombies + normals end;
- Execute the expressions that are executed just once, in left to right order as they
appear in the
Other Statement Macros
begin
[Statement]
Executes expressions in a body, in order.
- Macro Call:
-
begin
[ body ]end
- Arguments:
-
- body
bodybnf
- Values:
Zero or more instances of
<object>
.- Description:
Begin executes the expressions in a body, in order. The values of the last expression are returned. If there are no expressions in the body,
#f
is returned.
block
[Statement]
Executes a body with several options for nonstandard flow of control.
- Macro Call:
-
block (
[ exit-variable ])
[ block-body ]
[afterwards
[ afterwards-clause ] ]
[cleanup
[ cleanup-clause ] ]
{exception
exception-clause }*
end
[block
] - Arguments:
-
- exit-variable
variable-namebnf
- block-body
bodybnf
- afterwards-clause
bodybnf
- cleanup-clause
bodybnf
- exception-clause
-
(
[ name::
] type {,
exception-options }*)
[ bodybnf ] - name
variable-namebnf
- type
expressionbnf
- exception-options
-
{
test:
expressionbnf } | {init-arguments:
expressionbnf }
- Values:
Zero or more instances of
<object>
.- Description:
-
block
executes the expressions in the block-body in order, and then executes the optional afterwards-clause and cleanup-clause. Unless there is a nonlocal exit, block returns the values of the block-body, or#f
if there is no block-body.If exit-variable is provided, it is bound to an exit procedure (an object of type
<function>
) that is valid during the execution of the block body and the clauses. At any point in time before the last clause returns, the exit procedure can be called. Calling the exit procedure has the effect of immediately terminating the execution of the block, and returning as values the arguments to the exit procedure.The body of the afterwards-clause, if provided, is executed after the block-body. The values produced by the afterwards-clause are ignored. This is useful when you want to execute an expression for side-effect after the block-body has executed, but still want to return the values of the last expression in the block-body.
The body of the cleanup-clause, if provided, is executed after the block-body and afterwards-clause. Its values are also ignored. The cleanup clause differs from the afterwards clause in that its body is guaranteed to be executed, even if the execution of the block is interrupted by a nonlocal exit. There is no such guarantee for the afterwards-clause.
For example, the following code fragment ensures that files are closed even in the case of an error causing a nonlocal exit from the block body:
block (return) open-files(); if (something-wrong) return("didn't work"); end if; compute-with-files() cleanup close-files(); end block
The exception-clauses, if supplied, install exception handlers during the execution of the block-body, afterwards-clause, and cleanup-clause. If one of these handlers is invoked, it never declines but immediately takes a nonlocal exit to the beginning of the block, executes the expressions in its body and returns the values of the last expression or
#f
if the body is empty. Note that when the expressions in an exception body are executed, all handlers established by theblock
are no longer active. Note also that the cleanup clause of the block will be executed before the expressions of the handler body are executed.The type and exception-options are as for
let handler
. If present, name is bound to the condition during the execution of the handler's body.The exception clauses are checked in the order in which they appear. That is, the first handler will take precedence over the second, the second over the third, etc.
The following is a trivial use of an exception clause.
block () open-files(); compute-with-files() cleanup close-files(); exception (<error>) "didn't work"; end block
Errata: In the published book, the
cleanup
andexception
clauses are incorrectly ordered.cleanup
must come beforeexception
clauses.Dynamic Extent of Block Features
A block installs features that are active for different portions of the execution of the block.
- During the execution of the block body and the afterwards clause the exit procedure, exception clauses, and cleanup clauses are active.
- During the execution of the cleanup clause, the exit procedure and exception clauses are active.
- During the execution of a handler installed by an exception clause, the exit procedure is active.
Intervening Cleanup Clauses
When an exit procedure is called, it initiates a nonlocal exit out of its establishing block. Before the nonlocal exit can complete, however, the cleanup clauses of intervening blocks (blocks that have been entered, but not exited, since the establishing block was entered) must be executed, beginning with the most recently entered intervening block. Once the cleanup clauses of an intervening block have been executed, it is an error to invoke the exit procedure established by that block. The cleanup clauses of the establishing block are executed last. At that point, further invocation of the exit procedure becomes invalid, and the establishing block returns with the values that were passed to the exit procedure.
Note that a block statement may also be exited due to the execution of a handler clause. Before the exception clause is executed, intervening cleanup clauses are executed as described above (including any clause for the establishing block.) The exit procedure may be invoked during execution of exception clauses, in which case the argument values are immediately returned from the block (the cleanup clause already having been executed).
During the process of executing the cleanup clauses of the intervening blocks, any valid exit procedure may be invoked and may interrupt the current nonlocal exit.
All exception clauses are executed in the same dynamic environment. None of the handlers established in the block are visible during the execution of one of the handlers. This can be thought of as parallel installation of the handlers.
Restrictions on the use of exit procedures
The exit procedure is a first-class object. Specifically, it can be passed as an argument to functions, stored in data structures, and so on. Its use is not restricted to the lexical body of the block in which it was established. However, invocation of the exit procedure is valid only during the execution of the establishing block. It is an error to invoke an exit procedure after its establishing block has returned, or after execution of the establishing block has been terminated by a nonlocal exit.
In the following example, the
block
establishes an exit procedure in the bindingbar
. Theblock
returns a method containing a call tobar
, and the method is stored in the bindingfoo
. Callingfoo
is an error because it is no longer valid to invokebar
after its establishingblock
has returned.define constant foo = block (bar) method (n) bar(n) end; end block; foo(5) {error or other undefined consequences}
method
[Statement]
- Macro Call:
method
parameter-list [ body ]end
[method
]- Arguments:
-
- parameter-list
parameter-listbnf
- body
bodybnf
- Values:
An instance of
<method>
.- Description:
method
creates and returns a method specified by the parameter-list and body. For a complete description of methods, seeMethods
on page 80.