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.

Table 14-3 Statements

Macro

Description

Page

if

Executes an implicit body if the value of a test is true or an alternate if the test is false.

395

unless

Executes an implicit body unless the value of a test is true.

396

case

Executes a number of tests until one is true, and then executes an implicit body associated with the true test.

397

select

Compares a target object to a series of potential matches, and executes an implicit body associated with the first match found.

398

while

Repeatedly executes a body until a test expression is false.

399

until

Repeatedly executes a body until a test expression is true.

400

for

Performs general iteration over a body, updating bindings and performing end tests on each iteration.

400

begin

Executes expressions in a body, in order.

404

block

Executes a body with several options for nonstandard flow of control.

404

method

Creates and returns a method.

408

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, the if 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 by unless. If the value of test is true, the body is not executed and unless returns #f.

If there are no expressions in the body, then #f is returned.

unless(detect-gas? (nose))
    light(match)
end unless 

case [Statement]


Executes a number of tests until one is true, and then executes an implicit body associated with the true test.

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]


Compares a target object to a series of potential matches, and executes an implicit body associated with the first match found.

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, a select form that includes an otherwise 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]


Performs general iteration over a body, updating bindings and performing end tests on each iteration.

Macro Call:

for ( { for-clause } ,* |
      { { 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-value then 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:

  1. 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 is 1.
  2. 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.
  3. 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. If below is specified, the clause will be in bounds as long as the value is less than the bounds. If to 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. If to is specified with a negative increment, the clause will be in bounds as long as it is greater than or equal to the bounds.
  4. 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.
  5. 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 is until:, go to step 9.
  6. Execute the expressions in the body in order. The expressions in the body are used to produce side-effects.
  7. 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 +.
  8. 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.
  9. 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; 

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 the block 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 and exception clauses are incorrectly ordered. cleanup must come before exception 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 binding bar. The block returns a method containing a call to bar, and the method is stored in the binding foo. Calling foo is an error because it is no longer valid to invoke bar after its establishing block has returned.

define constant foo =
  block (bar)
     method (n) bar(n) end;
  end block;
foo(5)
  {error or other undefined consequences}

method [Statement]


Creates and returns a method.

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, see Methods on page 80.