GNU Emacs 32.0.50 (9008bf64c3) のマニュアルを PLaMo 翻訳で機械翻訳したものです。誤訳を含むことがあります。原文: https://www.gnu.org/software/emacs/manual/html_node/eintr/No-deferment-solution.html ・ emacs-doc.conao3.com

11.3.8 延期不可の解決策

操作の遅延という問題に対する解決策は、操作を遅延させない形で記述することである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 を返します。

このようなパターンは、コンピュータ上の多くのリソースを利用できる関数を記述する際に役立ちます。


脚注

(15)

末尾再帰という表現は、定数空間を用いるこのような処理を指す際に用いられる。

(16)

専門用語がやや紛らわしい。triangle-recursive-helperでは、再帰的な手続きの中で反復的な処理が行われている。この処理が反復的と呼ばれるのは、コンピュータがsum、counter、numberという3つの値を記録するだけで済むからであり、手続きが再帰的と呼ばれるのは、関数が自身を呼び出すからである。一方で、triangle-recursivelyで用いられている処理と手続きは、どちらも再帰的と呼ばれる。「再帰的」という言葉は、これら2つの文脈で異なる意味を持つ。