操作の遅延という問題に対する解決策は、操作を遅延させない形で記述することである15。これには異なるパターンでの記述が必要であり、多くの場合、初期化関数とヘルパー関数という2つの関数定義を記述することになる。
初期化関数がジョブをセットアップし、ヘルパー関数が処理を行います。
数値を足し合わせるための2つの関数定義です。非常にシンプルなので、かえって理解しにくいと感じています。
(defun triangle-initialization (number) "Return the sum of the numbers 1 through NUMBER inclusive. This is the initialization component of a two function duo that uses recursion." (triangle-recursive-helper 0 0 number))
(defun triangle-recursive-helper (sum counter number)
"Return SUM, using COUNTER, through NUMBER inclusive.
This is the helper component of a two function duo
that uses recursion."
(if (> counter number)
sum
(triangle-recursive-helper (+ sum counter) ; sum
(1+ counter) ; counter
number))) ; number
両方の関数定義を評価してインストールし、次に2行を指定してtriangle-initializationを呼び出します:
(triangle-initialization 2)
⇒ 3
初期化関数は、0、0、および三角形の行数となる数値の3つの引数を渡して、ヘルパー関数の最初のインスタンスを呼び出します。
ヘルパー関数に渡される最初の2つの引数は、初期化値です。これらの値は、triangle-recursive-helper が新しいインスタンスを生成する際に変更されます。16
1行の三角形の場合、どうなるか見てみましょう。(この三角形には、小石が1つ入っています!)
triangle-initialization は引数 0 0 1 を指定してヘルパーを呼び出します。その関数は、(> counter number) に関する条件判定を実行します:
(> 0 1)
その結果が偽であると判定されるため、if 節の else 部分が実行されます:
(triangle-recursive-helper
(+ sum counter) ; sum plus counter ⇒ sum
(1+ counter) ; increment counter ⇒ counter
number) ; number stays the same
まず以下を計算します:
(triangle-recursive-helper (+ 0 0) ; sum (1+ 0) ; counter 1) ; number
which is:
(triangle-recursive-helper 0 1 1)
ここでも (> counter number) は false となるため、再び Lisp
インタプリタは triangle-recursive-helper
を評価し、新たな引数を持つ新しいインスタンスを生成します。
この新しいインスタンスは、以下の通りとなります。
(triangle-recursive-helper
(+ sum counter) ; sum plus counter ⇒ sum
(1+ counter) ; increment counter ⇒ counter
number) ; number stays the same
which is:
(triangle-recursive-helper 1 2 1)
この場合、(> counter number)
の判定は真になります!そのため、インスタンスは和の値を返し、期待通り 1 となります。
それでは、triangle-initialization に引数 2
を渡して、2行の三角形に小石がいくつあるか確かめてみましょう。
その関数は(triangle-recursive-helper 0 0 2)を呼び出します。
段階的に、呼び出されるインスタンスは以下の通りとなります:
sum counter number
(triangle-recursive-helper 0 1 2)
(triangle-recursive-helper 1 2 2)
(triangle-recursive-helper 3 3 2)
最後のインスタンスが呼び出される際、(> counter number)
の判定は真となるため、そのインスタンスは sum の値である 3 を返します。
このようなパターンは、コンピュータ上の多くのリソースを利用できる関数を記述する際に役立ちます。
末尾再帰という表現は、定数空間を用いるこのような処理を指す際に用いられる。
専門用語がやや紛らわしい。triangle-recursive-helperでは、再帰的な手続きの中で反復的な処理が行われている。この処理が反復的と呼ばれるのは、コンピュータがsum、counter、numberという3つの値を記録するだけで済むからであり、手続きが再帰的と呼ばれるのは、関数が自身を呼び出すからである。一方で、triangle-recursivelyで用いられている処理と手続きは、どちらも再帰的と呼ばれる。「再帰的」という言葉は、これら2つの文脈で異なる意味を持つ。