Previous | Table of Contents | Next |
In most programming languages, a program consists of a sequence of instructions, each having some effect. Heres an example in a typical but imaginary language:
procedure distance { read(x, y); // First step: read the input data. dist = sqrt(x*x @@i\+ y*y); // Second step: perform a computation. print(dist); // Third step: print the results. }
Although it is possible to write a similarly structured program in Scheme, a typical Scheme program is not built out of instructions, but out of expressions, each producing a value. The user interacts with Scheme by typing an expression, to which Scheme responds by evaluating the expression and printing the result. The same computation in Scheme looks like this:1
1The grater-than sign (>) is Schemes prompt to the user.
> (define (distance x y) ; Define a function (sqrt (+ (* x x) (* y y))) ; whose body is an expression. DISTANCE ; The (define ...) is an expression too; ; this is its value. > (distance 3 4) ; Another expression using the function 5 ; and Schemes response.
The main advantage of an expression-based language is that you are not committed to printing the result of a computation. The same expression can provide an intermediate value in a larger expression:
> (+ (distance 3 4) (distance 5 12)) 18
Many other languages permit the definition of functions, but in Scheme the composition of functions is the primary control mechanism. Also, Schemes expression model encourages functional programming, which avoids operations with side effects such as changing the value of a variable. Functional programming avoids certain common categories of program bugs and makes the program easier to analyze for purposes such as optimization or verification.
In most programming languages, the operations that can be applied to data are represented by a combination of prefix notation:
sqrt(x) distance(x, y)
infix notation:
x + y x*x + y*y (x+x) * (y+y)
and occasionally postfix notation, such as the eponymous C++ increment operator. In Scheme, every operation is represented in prefix form:
(sqrt x) (distance x y) (+ x y) (+ (* x x) (* y y)) (* (+ x x) (+ y y))
Avoiding using infix operators also avoids having to learn the precedence of operators. (Typically, multiplication has precedence over addition, so that x+y*z means x+(y*z). The C language has 15 levels of precedence!)
It is this syntactic uniformity of Scheme that makes the language both quick to learn and simple to interpret. In Scheme, the plus sign (+) is just an ordinary procedure name, exactly comparable to the name sqrt for the square root procedure. In the examples in this chapter, only the parentheses (and spaces to separate tokens) have a special syntactic role.
Parentheses indicate the invocation of a procedure, except in certain special forms that begin with a special keyword. define is one of those; the parentheses in (distance x y) on the first line of the definition of distance do not call the procedure, which doesnt exist yet. With few exceptions, parentheses surround a sequence of subexpressions where the value of the first subexpression is a procedure to invoke and the values of the other subexpressions provide the argument values. Parentheses are never optional in Scheme; if theyre not required, theyre not allowed.
Most programming languages provide the capability of dynamic memory allocation, but in most languages, it is the programmers responsibility to also deallocate the memory. (The allocation operator may be called new or alloc; the deallocation operator may be delete or free.)
That approach can be problematic if the language allows a block of memory to be shared by multiple data structuresin other words, if the language allows the programmer explicit access to a pointer to the allocated block. In those languages, it is not always easy for the programmer to know whether all uses of a memory block are finished, a situation that gives rise to dangling pointer bugs (attempts to dereference a pointer to a freed block of memory). Similar bugs may arise even for memory automatically deallocated by the language implementation, such as local storage for a procedure call, if the program can retain a pointer to this deallocated area.
In Scheme, keeping track of which blocks of memory are in use is the job of the programming language implementation, not that of the programmer. This makes programs much faster to write, more readable, and much less bug-prone. Automatic storage management is, arguably, the main thing that distinguishes high-level from low-level languages. The Java language recently introduced this capability to the world of mainstream program development. The process by which the language manages memory allocation is called garbage collection.
Previous | Table of Contents | Next |