We have identified in Lisp some of the elements that must appear in any powerful programming language:
- Numbers and arithmetic operations are primitive data and procedures.
- Nesting of combinations provides a means of combining operations.
- Definitions that associate names with values provide a limited means of abstraction.
Now we will learn about procedure definitions, a much more powerful abstraction technique by which a compound operation can be given a name and then referred to as a unit.
We begin by examining how to express the idea of “squaring.” We might say, “To square something, multiply it by itself.” This is expressed in our language as
(define (square x) (* x x))
We can understand this in the following way:
(define (square x) (* x x))↑ ↑ ↑ ↑ ↑ ↑ To square something, multiply it by itself.
We have here a compound procedure, which has been given the name
square. The procedure represents the operation of multiplying
something by itself. The thing to be multiplied is given a local
name, x, which plays the same role that a pronoun plays in
natural language. Evaluating the definition creates this
compound procedure and associates it with the name
The general form of a procedure definition is
(define (<name> <formal parameters>) <body>)
The <name> is a symbol to be associated with the procedure definition in the environment. The <formal parameters> are the names used within the body of the procedure to refer to the corresponding arguments of the procedure. The <body> is an expression that will yield the value of the procedure application when the formal parameters are replaced by the actual arguments to which the procedure is applied. The <name> and the <formal parameters> are grouped within parentheses, just as they would be in an actual call to the procedure being defined.
square, we can now use it:
(square (+ 2 5))49
(square (square 3))81
We can also use
square as a building block in defining other
procedures. For example, x2 + y2 can be expressed as
(+ (square x) (square y))
We can easily define a procedure
sum-of-squares that, given any two numbers as arguments, produces the
sum of their squares:
(define (sum-of-squares x y) (+ (square x) (square y))) (sum-of-squares 3 4)25
Now we can use
sum-of-squares as a building block in constructing
(define (f a) (sum-of-squares (+ a 1) (* a 2))) (f 5)136
Compound procedures are used in exactly the same way as primitive
procedures. Indeed, one could not tell by looking at the definition
sum-of-squares given above whether
square was built into
the interpreter, like
*, or defined as a compound procedure.