Prev Index Next

Recursion

A Scheme procedure can invoke itself and we call this recursion. This powerful technique divides a problem into smaller pieces, which are simpler to solve and combine into a final result. Writing a recursive procedure is simple by sticking to three steps:

  1. define the procedure and the arguments;
  2. create the base check – a test for which the procedure returns;
  3. create the body of the procedure – such that it calls itself.

Your recursive procedure must never perpetually call itself (but sometimes this rule is broken). This is why a base check is a must. Consider these questions when writing a recursive procedure.

Suppose you wish to compute the factorial of a non-negative integer. Each step is then as follows.

Define the procedure and the arguments:

(define (factorial x)

Create the base check – a test for which the procedure returns:

(if (equal? x 0)
    1

Create the body of the procedure – such that it calls itself:

(* x (factorial (- x 1)))

Here is the whole procedure pretty-printed:

(define (factorial x)
  (if (equal? x 0)
      1
      (* x (factorial (- x 1)))))

Here is what happens. Procedure factorial calls itself with a smaller argument each time, until it reaches zero, at which point it returns unity. On each call, the return value is multiplied by the respective procedure argument. So, (* 1 1), (* 2 1), and (* 3 2). The original procedure invocation returns the final result.

> (factorial 3)
$1 = 6

Guile allows you to trace procedures. This means that every call to that procedure is reported to the user during a program run. Tracing is useful to determine whether a function is called with correct arguments.

Here is the trace of factorial.

> ,trace (factorial 3)
trace: |  (factorial 3)
trace: |  |  (factorial 2)
trace: |  |  |  (factorial 1)
trace: |  |  |  |  (factorial 0)
trace: |  |  |  |  1
trace: |  |  |  1
trace: |  |  2
trace: |  6

Another reason why tracing is useful is to determine whether a recursive procedure is tail-recursive. Without getting into specifics, it means that, for large x, a stack overflow error will not happen. Observe the pattern of the trace. When the trace shape evolves like a triangle, the procedure is not tail-recursive.

Why is that so? Every time the factorial procedure returns, the * procedure is called. What we want is to let the procedure factorial call itself in the final step. This can be done by moving * from outside the procedure call to within the procedure call, by introducing a new argument r, which keeps track of the result. Here is the tail-recursive procedure:

(define (factorial-iter x r)
  (if (equal? x 0)
      r
      (factorial-iter (- x 1) (* r x))))

In this case, the pattern of the trace is shaped like a rectangle, unlike that of a triangle.

> ,trace (factorial-iter 3 1)
trace: |  (factorial-iter 3 1)
trace: |  (factorial-iter 2 3)
trace: |  (factorial-iter 1 6)
trace: |  (factorial-iter 0 6)
trace: |  6

Since the factorial procedure should accept only one argument, the number, we define factorial-iter within the factorial procedure. Here is the final factorial version:

(define (factorial x)
  (define (factorial-iter x r)
    (if (equal? x 0)
        r
        (factorial-iter (- x 1) (* r x))))
  (factorial-iter x 1))

Let us invoke this new factorial with the number 7:

> (factorial 7)
$2 = 5040

Tail-recursion

Some recursive procedures can be computed in constant space, and we say such procedures are tail-recursive. Consider the number of times you can recursively call a procedure. This number is limited by your computer memory. However, tail-recursive procedures can be called an unbounded number of times. When writing computer programs in Scheme, we heavily rely on recursion, and without tail-recursion, programs would quickly run out of stack space. This is why Scheme implementations must be properly tail-recursive, which we can loosely translate as they run tail-recursive procedures in constant space.

We have learned about lambda expressions and procedure calls, which are also expressions. Here, we consider a special case of such expressions, which we say are tail expressions, tail calls, and self-tail calls.

Consider a lambda expression in form of (lambda (<args>) <body>). Suppose the body consists of n expressions: <exp-1>, <exp-2>, …, and <exp-n>. So, (lambda (<args>) <exp-1> <exp-2> ... <exp-n>). Also consider the if special form in form of (if <test> <consequent> <alternative>).

Implementations of Scheme are required to be properly tail-recursive. A Scheme implementation is properly tail-recursive if it supports an unbounded number of active tail calls. A call is active if the called procedure may still return.

Consider the factorial-iter procedure:

(define (factorial-iter x r)
  (if (equal? x 0)
      r
      (factorial-iter (- x 1) (* r x))))

Suppose you expand define with lambda:

(define factorial-iter
  (lambda (x)
    (if (equal? x 0)
        r
        (factorial-iter (- x 1) (* r x)))))

It is straightforward to see that the if special form, in factorial-iter, is the only expression in the body of the lambda expression. As it is the only expression, it is also a tail-expression. Since the if special form is a tail-expression, both the consequent and the alternative expressions are also tail-expressions.

The alternative expression is a tail-expression, and it is a procedure call, which means that it is a tail call. For this tail call, the procedure factorial-iter calls itself recursively, meaning that this is a self-tail call. Scheme implementations must be properly tail-recursive, meaning that this call runs in constant space. This is why the shape of the trace of factorial-iter is a rectangle.

In loose terms, the factorial-iter procedure will not cause a stack overflow error, unlike what may happen in languages such as C.

Consider the unoptimized factorial procedure:

(define (factorial x)
  (if (equal? x 0)
      1
      (* x (factorial (- x 1)))))

Suppose you expand define with lambda:

(define factorial
  (lambda (x)
    (if (equal? x 0)
        1
        (* x (factorial (- x 1)))))

It is straightforward to see that the if special form, in factorial, is the only expression in the body of the lambda expression. As it is the only expression, it is also a tail-expression. Since the if special form is a tail-expression, both the consequent and the alternative expressions are tail-expressions.

The alternative expression is a tail-expression, and it is a procedure call, which means that it is a tail call. But it is not a procedure call of factorial but that of *, which means that it is not an self-tail call. This procedure does not run in constant space.

Macro recursion

Macros can also expand themselves. Consider the following examples of the curry macro. The curry macro is named after currying, a concept in which a procedure of many arguments is rewritten as a sequence of procedures that accept only one argument.

Suppose the expression before expansion (EBE) is:

(curry (lambda (x) x) y)

Suppose the expression after expansion (EAE) is:

(lambda (y) (lambda (x) x))

Begin with:

(define-syntax curry (lambda (x) (syntax-case x ()

Write the macro body for the EBE:

(define-syntax curry (lambda (x) (syntax-case x ()
  ((curry exp var)

Introduce a “guard clause” to the pattern, to make sure that var is an identifier, by using the identifier? predicate procedure:

(define-syntax curry (lambda (x) (syntax-case x ()
  ((curry exp var) (identifier? #'var)

Write the macro body for the EAE:

(define-syntax curry (lambda (x) (syntax-case x ()
  ((curry exp var) (identifier? #'var)
   #'(lambda (var) (begin exp))))))

Expand to see macro usage:

> ,expand (curry (lambda (x) x) y)
$1 = (lambda (y) (lambda (x) x))

Now note that you can chain curry:

> ,expand (curry (curry (lambda (x) x) y) z)
$2 = (lambda (z) (lambda (y) (lambda (x) x)))

But also note how the curry creates the identity procedure (lambda (x) x):

> ,expand (curry x x)
$3 = (lambda (x) x)

Consider the following chain:

>,expand (curry (curry (curry body x) y) z)
$4 = (lambda (z) (lambda (y) (lambda (x) body)))

We can write this chain of curry calls as one macro. Let us write curry such that the EBE is:

(curry body x y z ...)

Now the EAE is:

(lambda (x) (lambda (y) (lambda (z) (... body))))

Begin with:

(define-syntax curry (lambda (x) (syntax-case x ()

Write the macro body for the EBE:

(define-syntax curry (lambda (x) (syntax-case x ()
  ((curry body var var* ...)

Introduce a “guard clause” to the pattern, to make sure that var is an identifier, by using the identifier? predicate procedure.

(define-syntax curry (lambda (x) (syntax-case x ()
  ((curry body var var* ...) (identifier? #'var)

Write the macro body for the EAE. Let curry call itself:

(define-syntax curry (lambda (x) (syntax-case x ()
  ((curry body var var* ...) (identifier? #'var)
   #'(lambda (var) (curry body var*)))

And now handle the base check:

(define-syntax curry (lambda (x) (syntax-case x ()
  ((curry body var) (identifier? #'var)
   #'(lambda (var) body))
  ((curry body var var* ...) (identifier? #'var)
   #'(lambda (var) (curry body var* ...))))))

Note that the base check must come before the macro body (curry body var var* ...) that accepts an arbitrary number of arguments.

Let us now test the macro by expanding:

> ,expand (curry body x)
$7 = (lambda (x) body)
> ,expand (curry body x y)
$8 = (lambda (x) (lambda (y) body))
> ,expand (curry body x y z)
$9 = (lambda (x) (lambda (y) (lambda (z) body)))

The guard clause makes it illegal to call curry with anything that is not an valid identifier:

> ,expand (curry body 1)
While executing meta-command:
Syntax error:
unknown file:40:8: source expression failed to match any pattern in form (curry body 1)

Prev Index Next