(workshop logo)

Interacting with Chez Scheme

How to invoke the Chez Scheme interface on MathLAN

The Scheme implementation that we'll be using during class sessions is called Chez Scheme. On MathLAN, one can run Chez Scheme in the hpterm terminal emulator. The command is scheme, followed by zero or more file names:

bourbaki% scheme
Chez Scheme Version 5.0c
Copyright (c) 1994 Cadence Research Systems

>
Chez Scheme is implemented as an interactive interface to the compiler. It first compiles and executes the Scheme source code or object code contained in the files named on the command line, then issues a prompt for further instructions. At this point, you can press <Control/D> if you wants to exit from the interactive interface and return to the shell. (Careful -- an extra <Control/D> will take you out of the shell too.)

Otherwise, you type in definition or an expression after the prompt. A Scheme program has an extremely simple top-level structure: It is a sequence of zero or more definitions and expressions. So the Scheme compiler can treat what is typed in by the programmer as a legal Scheme program that is simply being submitted one constituent at a time. In particular, the Chez Scheme compiler performs incremental compilation of this program, one constituent at a time.

If you give the Chez Scheme interactive interface a definition, it compiles it, stores the result in an internal data structure called an environment, and generates another prompt. If you give it an expression, it compiles and evaluates the expression and prints out the value, then generates another prompt.

Definitions

A Scheme definition begins with a left parenthesis, followed immediately by the keyword define, and ends with a right parenthesis. Here are two examples. The first defines a global variable distance and initializes it to 200. The second defines a function (in Scheme terminology, a ``procedure'') named cube that takes any number as argument and returns its cube.

(define distance 200)

(define (cube n)
  (* n n n))
A Scheme definition can occupy several lines; pressing the <Enter> key does not terminate the definition. Instead, the interactive interface collects all of the input it sees, parsing as it goes, until it detects the right parenthesis that matches the initial left parenthesis; only then does it start compiling. The interface does not respond in any way until it sees that right parenthesis. (However, a programmer who bogs down in the middle of an attempted definition can press <Control/C>, which causes the interactive interface to discard the part of the definition that it has so far collected and to generate a new prompt.)

Variables

Instead of a definition, you can give the interactive interface an expression to be evaluated. There are many kinds of expressions in Scheme. One common kind is a global variable:
> distance
200
>
This presupposes, of course, that the variable has already been defined. (Scheme variables are always explicitly initialized at the point of definition, so you don't have to worry about seeing a variable that has been defined but still contains garbage.) If you try to evaluate a variable that has not been defined, you get an error message instead of a value, but the interface generates a new prompt as usual:
> ditsance

Error: variable ditsance is not bound.
Type (debug) to enter the debugger.
> 
Scheme permits a much larger variety of characters to appear inside identifiers than most programming languages do, because in general it does not permit operators to be placed right next to identifiers, without intervening spaces. In particular, the hyphen is commonly used as a break character; it does not denote subtraction unless it is standing by itself. Thus i-j and top-of-sequence are simple variables, not subtraction expressions. Asterisks, exclamation points, question marks, dollar signs, and colons are among the many other characters that are legal inside variable names and are frequently so used.

Chez Scheme predefines a few hundred procedures, and if you happen to type in the name of a predefined procedure, Chez Scheme evaluates the procedure name, getting the procedure as a data object, and does its best to print out a representation of that procedure:

> sqrt
#<system procedure sqrt>
>
This is not an error from the language's point of view, but it may not be what the programmer had in mind either.

Procedure calls

Another common type of expression is the procedure call (i.e., function call). A Scheme procedure call consists of a left parenthesis, one or more expressions, and a right parenthesis; the value of the first of the enclosed expressions is the procedure to be called, and the values of the remaining expressions are the arguments to the procedure. Here's a procedure call that applies the predefined procedure sqrt to the argument 1000:

> (sqrt 1000)
31.622776601683793
>
Even elementary arithmetic operations are treated syntactically as procedure calls in Scheme, giving arithmetic expressions an appearance that novices often find strange:

> (+ (- 8 3)
     (* 7 4))
33
>
As this example shows, procedure calls can be nested. In Scheme, deeply nested procedure calls are considerably more frequent than in procedural languages like Pascal and C. This is not a symptom of bad style in Scheme and should not necessarily be reprehended. Note also that expressions, like definitions, can occupy more than one line.

A common beginner's error is to try to infix arithmetic operators. The result looks different depending on whether the programmer remembers to enclose the expression in parentheses:

> (3 + 8)

Error: attempt to apply non-procedure 3.
Type (debug) to enter the debugger.
>
Here the compiler notices the left parenthesis at the beginning of the expression (3 + 8) and decides, correctly, that it is looking at a procedure call; it then evaluates the expressions between the parentheses and finds that the first of the values, which is supposed to be a procedure, is instead a number. This is an error.

> 3 + 8
3
> #<system procedure +>
> 8
>
In the absence of parentheses, the compiler regards the numeral 3 as a complete and self-sufficient expression, evaluates it to get the number 3, prints out that value, and generates a prompt. But, since it hasn't exhausted the input already typed, it immediately sees the plus sign, which is also a complete and self-sufficient expression, the name of the predefined addition procedure. So it evaluates that name, getting the procedure as value, writes out a representation of the procedure, and generates another prompt. But there is still more input; the compiler immediately sees the numeral 8, evaluates it, prints out the result, and generates yet another prompt.

You get both of these effects at once if you forget the procedure-call syntax and write something like

> sqrt (1000)
#<system procedure sqrt>
>
Error: attempt to apply non-procedure 1000.
Type (debug) to enter the debugger.
>
The compiler sees the input as two independent expressions, of which the first, sqrt, is legal and evaluates to a predefined procedure, while the second, (1000), is illegal, since it has the form of a procedure call (to a zero-argument procedure), but has a number rather than a procedure as the value of its first (and only) enclosed sub-expression.

A list of Scheme's predefined procedures can be found in the section on standard procedures of the Revised(4) report on the algorithmic language Scheme, the de facto standard for the language.

Calls to programmer-defined procedures have the same form as calls to predefined procedures:

> (cube 38)
54872
>

Control expressions

Besides variables, literal constants, and procedure calls, Scheme provides a number of kinds of expressions that impose control flow on subexpressions. Perhaps the most common of these is the two-way conditional expression, which consists of a pair of parentheses enclosing the keyword if and three sub-expressions:

(if (= denominator 0)
    0
    (quotient numerator denominator))
The first of the sub-expressions, the test of the conditional, determines which of the other two sub-expressions (the consequent and the alternative) should be evaluated: If the test is true, the consequent provides the value of the if-expression, whereas if the test is false, the alternative provides that value. C programmers will recognize this construction -- it's the Scheme counterpart of the C expression

(denominator == 0) ? 0 : (numerator / denominator)
Other Scheme control expressions have various internal structures, but each one begins with a left parenthesis and a keyword and ends with a right parenthesis. The keywords, which may not be used as variables in Scheme, are and, begin, case, cond, do, if, lambda, let, let*, letrec, or, quasiquote, quote, and set!.

Comments

A comment in Scheme begins with a semicolon and ends at the end of the line. A ``multi-line comment'' is really just a sequence of one-line comments; you need a new semicolon on each line.

Chez Scheme object files

A Scheme source code file is re-compiled every time it is loaded. Once a program has been completely debugged, it can be reloaded much more quickly if it is loaded in the form of an object file -- basically, a pre-compiled program. Under Chez Scheme, object files conventionally have names ending in .so.

To create an object file frogs.so from a Scheme source code file frogs.ss, call the compile-file procedure, giving it as argument a string containing the name of the program file (without the filetype suffix):

> (compile-file "frogs")
> 
Compilation affects only the loading time, not the execution time, since Chez Scheme compiles every definition as soon as it sees it anyway.


Workshop front door ... Editing ... Debugging ... Scheme bibliography


created June 5, 1996
last revised December 5, 1996

John David Stone (stone@math.grin.edu)