1.1.4 Compound Procedures


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 abstration.
  • 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:

    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 square. 12

    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. 13 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. 14 The <name> and the <formal parameters> are grouped within parentheses, just as they would be in an actual call to the procedure being defined.

    Having defined square, we can now use it:

    (square 21)

    (square (+ 2 5))

    (square (square 3))

    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)

    Now we can use sum-of-squares as a building block in constructing further procedures:

    (define (f a)
      (sum-of-squares (+ a 1) ( a 2)))

    (f 5)

    Compound procedures are used in exactly the same way as primitive procedures. Indeed, one could not tell by looking at the definition of sum-of-squares given above whether square was built into the interpreter, like + and *, or defined as a compound procedure.