[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

11. ループと再帰

Emacs Lisp は、一つないしは複数のS式を繰り返し評価させるのに、主として 二つの方法を持っている。一つは while ループを利用するもの、もう一 つは再帰 (recursion) を使うものである。

繰り返しは非常に役に立つものである。例えば四つの文の分だけ先に移動したい 場合には、一つの文だけ移動するプログラムを書いて、そのプロセスを四回繰り 返せば良い。計算機には飽きるとか疲れるとかいった感情はないので、このよう な繰り返しをいくらやらせたところで、人間のように過度の繰り返しをさせてし まってミスを犯しやすくなるといった弊害は無い。

11.1 while  特定のコードの繰り返し
11.2 再帰  
11.3 ループについての練習問題  


11.1 while

特殊形式 while は、最初の引数を評価して返された値が真か偽かを テストする。ここまでは if の時と同じである。しかし、次に Lisp イ ンタプリタがやることはちょっと違っている。

while 式においても、返された値が偽の場合は、Lisp インタプリタは残 りのS式 (このS式の本体部分) をスキップし、それを評価したりはしない。し かし、返された値が真の場合は、Lisp インタプリタは本体部分を評価した後、 もう一度 while の最初の引数が真か偽かをテストする。もし、返された 値がまたもや真であったなら、Lisp インタプリタは再度本体部分を実行する。

while 式のテンプレートは次の通りである。

 
(while 真偽テスト
  本体...)

while 式の真偽テストの部分が真を返している限り、本体部分は繰り返 し評価される。このプロセスはループと呼ばれる。これは、Lisp インタプリタ が何回も同じことを繰り返す様子が、飛行機の宙返りと似ているためである。 真偽テストの結果が偽の場合は、Lisp インタプリタはそこで while 式 の残りの部分を評価するのを止めて「ループを抜ける」。

当たり前のことだが、もし while 式の最初の引数が常に真を返すとする と、その後の本体部分は何回でも無限に評価され続ける。逆にもし真になり得な いとすると、本体部分の式は決して評価されることはない。while 式 のループを書く工程とは、本体部分のS式の評価を繰り返したい回数だけ真偽テ ストが真の値を返し、そしてその後、偽を返すような仕掛けを作ることである。

while 式を評価した場合に返される値は真偽テストが返す値である。従っ て面白いことに、エラーなく評価された while ループは、たとえ100回 ループを繰り返した後でも一度もループしなかった場合でも、必ず nil を返す。正しく評価された while 式は決して真の値を返すことはないわ けだ! このことは、while 式は常に副作用を目的として評価されること を意味する。即ち、while ループの中の本体部分のS式を評価するため に評価されるのである。これは理にかなっている。単にループを繰り返すことが 目的なのではなく、ループの中のS式を繰り返し評価した時に生じる結果こそが 望むものなのだから。

11.1.1 while ループとリスト  
11.1.2 リストを使ったループの例: print-elements-of-list  while, car, cdr の利用
11.1.3 増加するカウンタを使ったループ  
11.1.4 減少するカウンタを使ったループ  


11.1.1 while ループとリスト

while ループをコントロールする一般的な方法は、リストが要素を持っ ているかどうかをテストすることである。もし要素が一つもなければ、それは空 リストであり、結果として空リスト () が返される。これは、 nil もしくは偽 (false) の同義語である。一方、要素を持つリストの場 合は、それらの要素そのものが返される。Lisp は nil 以外のものは全 て真と見倣すので、この場合は while ループでは真が返されたことにな る。

例えば、次のS式を評価すると、変数 empty-list の値を nil にセットすることが出来る。

 
(setq empty-list ())

この setq 式を評価した後、変数 empty-list をいつものように カーソルをこのシンボルのすぐ後に持っていって C-x C-e とタイプすこ とで評価してみよう。エコー領域に nil が表示されるはずだ。

 
empty-list

一方、変数を要素を持つリストにセットした場合は、その変数を評価するとその リストが表示される。これは次の二つのS式を評価することで確かめられる。

 
(setq animals '(giraffe gazelle lion tiger))

animals

ということで、リスト animals に要素があるかをテストに使用するよう な while ループを作るには、ループの最初の部分を次のように書けばよ い。

 
(while animals
       ...

while が最初の引数をテストすると、変数 animals が評価され る。これはリストを返す。このリストが要素を持っている限り、while はテストの結果が真だと判断する。しかし、リストが空になると、結果は偽だと 判断される。

while ループが無限に続くことを避けるためには、最終的にリストが空 になるような何らかの仕掛けが必要である。よく使われるテクニックは、 while 式の本体部分の形式の一つでそのリストの値をそのリストの cdr で置き換えるようにしておくことである。cdr 関数が評価さ れるごとにそのリストは短くなっていき、ついには空リストが残されるというわ けである。そうなると while ループのテストは偽を返し、while の引数はもはや評価されず、ループが終了する。

例えば、動物のリストが変数 animals にバインドされていた時は、次 のS式を使ってその変数を元のリストの CDR にセットすることが出来る。

 
(setq animals (cdr animals))

もし一つ前のS式を評価してあったなら、このS式を評価するとエコー領域に (gazelle lion tiger) が表示されるはずだ。もう一度このS式を評価す ると、今度は (lion tiger) が表示される。更に評価すると、 (tiger) が表示され、そこでまた評価すると、やっと空リストになって、 nil が表示される。

cdr 関数を繰り返し使うことで最終的に真偽テストで偽を返す while ループのテンプレートは次のようになる。

 
(while リストが空かどうかのテスト
  本体...
  リストに自分自身の cdr をセット)

このテストと cdr の利用を組み合わせて、リストの各要素を各々一行ご とに表示するような関数を作ることが出来る。これを次に説明することにしよう。


11.1.2 リストを使ったループの例: print-elements-of-list

リストを使った while ループのことを理解するには print-elements-of-list 関数を見ると良い。

この関数は、出力を表示するために幾つかの行を必要とする。エコー領域は一行 しかないので、今まで説明して来たように Info の中で評価するようなやり方で はその動作をうまく描写出来ない。代わりに必要なS式を `*scratch*' バッ ファにコピーして、そこでそれらを評価する必要がある。コピーするには、まず 対象となるリージョンの最初を C-SPC (set-mark-command) でマークしてからカーソルをリージョンの最後に移動し、M-w (copy-region-as-kill) でそのリージョンをコピーする。そして `*scratch*' バッファで C-y (yank) することで、そのS式 を取り出せばよい。

`*scratch*' バッファにそのS式をコピーしたら、今度はそのS式を評価 するのだが、最後の (print-elements-of-list animal)C-u C-x C-e とタイプして評価する必要がある。つまり eval-last-sexp に 前置引数を与えるのである。すると、評価した結果はエコー領域ではなく *scratch* バッファに表示される。(単に C-x C-e とするだけだ と、返された値はエコー領域に ^Jgiraffe^J^Jlion^J^Jtiger^Jnil のよ うに表示されてしまう。ここで出て来る `^J' は改行を表している。従っ て *scratch* バッファでは各々の単語は一行ごとに表示されることにな るわけである。このことは、今 Info バッファで次のS式を評価してみても確か められる。)

 
(setq animals '(giraffe gazelle lion tiger))

(defun print-elements-of-list (list)
  "Print each element of LIST on a line of its own."
  (while list
    (print (car list))
    (setq list (cdr list))))

(print-elements-of-list animals)

これらの三つのS式を順に `*scratch*' バッファで評価していくと、その バッファの中で次のように表示されるはずだ。

 
giraffe

gazelle

lion

tiger
nil

リストの各要素が各々一行ごとに表示され (これは print の仕事である)、 そして最後にこの関数自体が返した値が表示される。この関数の中の最後のS式 は while ループであり、while ループは常に nil を返 すので、nil がリストの最後の要素の後に表示される。


11.1.3 増加するカウンタを使ったループ

ループは止まるべき時に止まってくれないことには役に立たない。リストを使っ たループの制御以外の一般的な方法としては、最初の引数として、正しい回数だ け繰り返すと偽を返すテストを書くという方法がある。これは、ループがカウン タ---つまりループを繰り返した回数を数えるS式---を持つということである。

真偽テストとしては、(< count desired-number) といったS式が使える。 これは count の値が期待する繰り返しの数 desired-number よ りも小さければ、t という真の値を返し、desired-number 以上 であれば、偽の値 nil を返す。カウンタを増加させるS式は (setq count (1+ count)) といったごく簡単な setq 式でよい。 ここで 1+ は Emacs Lisp の組み込み関数で、引数に1を加えるものであ る。((1+ count)(+ count 1) としても同じだが、この方が 人間にとって読みやすいであろう。)

ということで、増加するカウンタを使った while ループのテンプレート は次のようになる。

 
カウンタを初期値に戻す
(while (< count desired-number)         ; 真偽テスト
  本体...
  (setq count (1+ count)))              ; インクリメンタ

この場合 count の初期値を定めなければならないことに注意しよう。通 常は1にセットする。

増加カウンタの例  
関数定義の各部分  
各部分の総合  


増加カウンタの例

あなたは浜辺で遊んでおり、小石で三角形を作ろうと思ったとしよう。まず、一 行目に1つの小石を置き、二行目に 2 つ置き、三行目に 3 つ置き..., という具 合に続けていくのだ。図で描くと次のようになる。

 
                ・
               ・・
              ・・・
             ・・・・

(約2500年前、ピタゴラス一派はこのような問題を考えて数論の初歩を発展させ ていった。)

ここで、七行の三角形を作るにはいくつの小石が必要かを知りたいと思ったとする。

当然、ここであなたがしなければならないのは1から7までの数を加えることであ る。これを行うには二通りの方法がある。小さい数から始めて1、2、3、4、... という数列を加えて行くか、大きい方から始めて7、6、5、4、... という数列の 和を取るかである。どちらの場合にも while ループを書く時の共通の方 法を説明してくれるので、この下から登るのと、上から降りるのと両方の例を作っ てみようと思う。最初の例として1に2、3、4を加えていくものから始めよう。

和を取る数のリストが短い場合は、一度に全部足してしまうのがもっとも簡単な 方法である。しかしながら、もし前もって数のリストがどれくらいの長さになる か分らなかったり、あるいは極めて長いリストの場合にも対処したい場合には、 複雑なプロセスを一度にやるのではなく、単純なプロセスを沢山繰り返して和を 求めるような方法を考える必要がある。

例えば、小石を一度に全部加える代わりに、まず最初の行の小石の数1に二行目 の小石の数2を加え、次にその合計に三行目の小石の数3を加え、今度はその和に 四行目の数4を加え、という操作を続けるのである。

このプロセスで大切な特徴は、繰り返し行う操作は単純ですむということである。 今の場合、各々のステップでやっていることは、二つの数を加えるということだ けである。その時点での行の小石の数と、それまでの合計は既に分っている。こ の二数の和を取るプロセスは、最後の行の数をそれまでの合計に加えるまで何回 でも繰り返される。もっと複雑なループでは、繰り返し行う操作はそれ程単純で はない。しかしそれでも全てを一度にやるよりはずっと簡単なのである。


関数定義の各部分

前節での分析は、我々の関数定義の骨組みを与えてくれる。まず、小石の全体の 数を表わす変数が要る。これは total としていいだろう。これがこの関 数の返す値である。

次に、この関数には三角形の全行数を表わす引数が要る。これは number-of-rows として良いだろう。

最後に、カウンタとして使う変数が必要である。これは counter として も構わないのだが、それよりも row-number とする方が良い。何故なら、 このカウンタがやることは行を数えることであり、プログラムは出来るだけ分り やすく書くべきものだからである。

Lisp インタプリタがこの関数内のS式の評価を開始すると、まず total の値が零にセットされる。これはまだ何も加えていないからである。次に、最初 の行の小石の数が足される。そして二行目の数を加え、三行目の数を加え、とい うふうに続き、最後の行の小石を加えたところで終了する。

totalrow-number も共にこの関数内部でしか使われない。従っ て、let を使って局所変数として宣言し、初期値を与えればよい。明ら かに total の初期値は0であり、また row-number の初期値は1 である。ということで、let 式は次のようになる。

 
  (let ((total 0)
        (row-number 1))
    本体...)

内部変数が宣言されて初期値にバインドされたなら、while ループを開 始する。テストの部分のS式は row-numbernumber-of-rows 以下である場合その時のみ t を返すようなものでなければならない。 (row-numbernumber-of-rows が一致する場合も含めないと最 後の行の小石の数が加えられないことに注意。)

Lisp には <= という、最初の引数が二番目の引数以下の場合に真を返し、 そうでない場合は偽を返す関数がある。これを使うと、while 式のテス ト部分のS式は次のように書ける。

 
(<= row-number number-of-rows)

全体の小石の数は、各々の行の小石の数をそれまでの合計に繰り返し加えていく ことで計算される。ある行の小石の数はその行の番号と一致するので、全体の数 は行の番号を加えていくことで求まることになる。(言うまでもないが、状況が 複雑な場合には、ある行の小石の数とその行の番号とはもっと複雑な関係で結ば れている。そういう場合は行番号は他の適当なS式で置き換えることになる。)

 
(setq total (+ total row-number))

このS式では total の新しい値としてそれまでの合計に現在の行の小石 の数を加えたものをセットしている。

total の値をセットしたら、次のループに移る場合のために条件を整え ておかなければならない。それには、カウンタ用の変数 row-number の 値に1を加えれば良い。row-number の値が1増やされると、次のループの while 式の先頭の真偽テストで、この新しい値が number-of-row の値以下であるかが判定される。もしそうであれば、変数 row-number の新しい値が前回のループでの total の値に加えられる。

Emacs Lisp の組み込み関数 1+ は数に1を加えてくれる。従って、 row-number 変数は次のS式で1増加させることが出来る。

 
(setq row-number (1+ row-number))


各部分の総合

これまでで関数定義の各々の部分を書いたので、ここでそれらを一つにまとめる ことにしよう。

まず while 式の中身は次の通り。

 
(while (<= row-number number-of-rows)   ; 真偽テスト
  (setq total (+ total row-number))
  (setq row-number (1+ row-number)))    ; インクリメンタ

これに let 式の変数リストを加えればほぼ関数定義の本体部分は完成す るが、最後にほんの少しだけ加えるべき要素がある。

それは変数 total それ自身を while 式の後に書くことである。 そうしないと関数全体が返す値は let 式の本体部分の最後のS式が返す 値、即ち while 式が返す値になるので、常に nil が返されてし まうからである。

このことはぱっと見ただけでは気がつかないかもしれない。値を1増加させる式 が最後だから良いように思えるかもしれないが、これはあくまで while 式の一部なのである。シンボル while から始まるリストの最後の要素な のだ。更に while ループ全体は let 式の本体部分の中のリスト なのである。

大ざっぱに書くと関数は次のような形をしている。

 
(defun 関数名 (引数リスト)
  "説明文字列..."
  (let (変数リスト)
    (while (真偽テスト)
      while の本体... )
      ... )                     ; ここに最後の式が来る。

このように定義された関数が返す値は let 式が返す値になる。というの も letdefun 以外の他のどのリストにも含まれてはいないか らである。だから、もし whilelet 式の本体の最後のS式で あれば、この関数は常に nil を返してしまうことになる。これは我々が 望む結果ではない! これを回避するには、単にシンボル totallet で始まるリストの最後に置けば良い。この式はこのリストの他のS 式が評価された後に評価される。これによって、全体として正しい値が返される ことになる。

let で始まるリスト全体を一行で表示してみると、理解しやすいだろう。 こう書くと変数リスト varlistwhile 式が let 式の 二番目と三番目の要素であることがよく分る。そして最後の要素が total になるわけである。

 
(let (変数リスト) (while (真偽テスト) while の本体... ) total)

以上を一つにまとめると、triangle 関数の定義は次のようになる。

 
(defun triangle (number-of-rows)    ; インクリメンタを使った
                                    ;   バージョン。
  "Add up the number of pebbles in a triangle.
The first row has one pebble, the second row two pebbles,
the third row three pebbles, and so on.
The argument is NUMBER-OF-ROWS."
  (let ((total 0)
        (row-number 1))
    (while (<= row-number number-of-rows)
      (setq total (+ total row-number))
      (setq row-number (1+ row-number)))
    total))

これを評価して triangle をインストールした後、実際に試してみよう。 二つの例を挙げる。

 
(triangle 4)

(triangle 7)

最初の4つの数の和は10になり、最初の7つの数の和は28になる。


11.1.4 減少するカウンタを使ったループ

while ループのテスト部分のもう一つの一般的な書き方としては、カウ ンタが零を越えるかどうかを判定するというものがある。カウンタが零より大き い間はループを繰り返すが、零以下になると止まるというわけである。このように 動作させるためにはカウンタは零以上の数から始めて繰り返しの部分が評価され るごとに小さくなるようにしなければならない。

テスト部分は (> counter 0) のようになる。これは counter の 値が零よりも大きければ真として t を返し、零以下なら偽として nil を返すというものである。数を次第に小さくしていくには、 (setq counter (1- counter)) という単純な setq 式を使う。こ こで、1- は引数を1減らす Emacs Lisp の組み込み関数である。

ということで、while ループのテンプレートは次のようになる。

 
(while (> counter 0)                    ; 真偽テスト
  本体...
  (setq counter (1- counter)))          ; デクリメンタ

減少するカウンタを使った例  
関数定義の各部分   
各部分の総合   


減少するカウンタを使った例

減少するカウンタを使ったループを説明するために、triangle 関数を、 このようなカウンタを使って書き直してみることにする。

数え方は、この関数の前回のバージョンの逆である。今回は、例えば三行からな る三角形の小石の数を求めるために、まず三行目の3つの小石を加え、次にその 前の二行目の2個の小石の数を加え、更にその合計に1行目の小石の数1を加える という操作をすることになる。

同様にして、七行からなる三角形の小石の数を求めるには、七行目の小石の数7 にその前の行の小石の数6を加え、次にその合計に その前の行に小石の数5を加 え、とやっていくことになる。前回の例と同じく、各々の足し算ではそれまでの 合計と現在の行の小石の数の二つの数を加えているだけである。このプロセスは、 足す小石の数がなくなるまで繰り返される。

最初に加える小石の数も分る。最後の行の小石の数は全体の行の数に等しいから である。三角形が七行からなっていれば、最終行の小石の数は7である。同様に、 次々と足していく小石の数も分る。それは、前回足した数から1を引いたもので ある。


関数定義の各部分

まず三つの変数が必要である。三角形の行の総数、各々の行の小石の数、そして、 小石の総数だ。この最後の数が今回求めようとしている数である。これら三つの 変数を各々 number-of-rows, number-of-pebbles-in-row, total と名付ることにする。

totalnumber-of-pebbles-in-row もこの関数の内部でしか使 われないので、let を用いて宣言される。total の初期値は勿論 零である。一方、number-of-pebbles-in-row の初期値は三角形の行の数 に等しくなるべきである。これは、もっとも長い行から小石の数を数えていくた めである。

従って let 式の始めの部分は次のようになる。

 
(let ((total 0)
      (number-of-pebbles-in-row number-of-rows))
  本体...)

小石全体の数は、各々の行の小石の数を順番に数えていくことで求められる。 つまり、繰り返し次のS式を評価していけばよい。

 
(setq total (+ total number-of-pebbles-in-row))

number-of-pebbles-in-rowtotal に加えられた後、次のルー プに備えて一つだけ値を減らさなければならない。というのも次に加えられる行 は一段上の行であり、現在の長さよりも一つ分短いからである。これは次のS式 を評価することでなされる。

 
(setq number-of-pebbles-in-row
      (1- number-of-pebbles-in-row))

最後に、小石が無くなった時点で while ループを抜けなければならない が、そのための条件部は次のような簡単なものでよい。

 
(while (> number-of-pebbles-in-row 0)


各部分の総合

以上のS式をひとまとめにすると、関数定義は次のようになる。

 
;;; デクリメンタを使った最初のバージョン。
(defun triangle (number-of-rows)        
  "Add up the number of pebbles in a triangle."
  (let ((total 0)
        (number-of-pebbles-in-row number-of-rows))
    (while (> number-of-pebbles-in-row 0)
      (setq total (+ total number-of-pebbles-in-row))
      (setq number-of-pebbles-in-row
            (1- number-of-pebbles-in-row)))
    total))

これはこれで、うまく動作する。

しかしながら、実は変数 number-of-pebbles-in-row は必要でない!

triangle 関数が評価されると、シンボル number-of-rows は初期 値を与えられて、ある数にバインドされる。この数は関数の本体の中であたかも 局所変数であるかのごとく変化させることが可能で、この関数の外でのこの変数 の値には何の影響も与える心配はない。これは Lisp の大変便利な特徴であるが、 このことから関数内の number-of-pebbles-in-row を全て変数 number-of-rows で置き換えても良いことが分る。

ということで、以下に、この関数の少し整理したバージョンを挙げる。

 
(defun triangle (number)                ; 二番目のバージョン。
  "Return sum of numbers 1 through NUMBER inclusive."
  (let ((total 0))
    (while (> number 0)
      (setq total (+ total number))
      (setq number (1- number)))
    total))

まとめてみよう。きちんと書かれた while ループは以下の三つの部分か らなる。

  1. ループを正しい回数繰り返した後、偽を返すテスト

  2. 繰り返し評価された後、最後に望む値を返すようなS式

  3. 正しい回数だけループを繰り返した後にテストが偽を返すよう、真偽テストに渡 される値を変化させるS式


11.2 再帰

再帰関数とは、自分自身を評価するようなコードを含んでいるものである。関数 が自分自身を評価した場合、それはまた自分自身を評価するコードに出くわす。 結果としてその関数はまた自分自身を評価し ... となってこれがずっと続 く。再帰関数は自分自身止める条件を与えられない限り、永久に自分自身を繰り 返し呼び出し続けることになる。

再帰関数は、典型的には次のような三つの部分からなる条件分岐部を含んでいる。

  1. この関数がもう一度呼び出されるかどうかを決定する真偽テスト。ここでは do-again-test と呼ぶ。

  2. この関数の名前

  3. 条件分岐部が正しい回数だけ繰り返しを行った後に偽を返すようにするためのS 式。ここでは next-step-expression と呼ぶ。

再帰関数は、他の種類の関数に比べて最も簡単な形に書ける。実際、再帰関数を 使い始めると、しばしば奇妙な程単純な形になってしまい、不可解な感じがする ことが多いようである。再帰関数の定義を読むためには、ある種のコツが必要で、 最初は難しく思えても、慣れると単純であることが分ってくる。初めて自転車に 乗るときと同じである。

再帰関数のテンプレートは次のようになる。

 
(defun 再帰関数名 (変数リスト)
  "説明文字列..."
  本体...
  (if do-again-test
    (再帰関数名 
         next-step-expression)))

再帰関数が評価されるごとに、ある引数が next-step-expression の値にバイン ドされ、そしてその値が do-again-test で使われる。next-step-expression は 関数をもう繰り返す必要がなくなった場合に do-again-test が偽を返すように 設計されている。

do-again-test は 停止条件 (stop-condition) と呼ばれることも ある。これはテストが偽の場合に繰り返しが止まるからである。

11.2.1 List を使った再帰  再帰のテストにリストを使う
11.2.2 カウンタの代わりに再帰を使う  while ループを再帰で置き換える
11.2.3 cond を使った再帰の例  別の条件分岐を用いた再帰の例


11.2.1 List を使った再帰

先に挙げた、 while ループを使ったリストの要素を表示する関数の例は、 再帰的に書くことも可能である。次に、変数 animals の値をあるリスト にセットするS式も含めて、そのコードを書いてみることにしよう。

この例は `*scratch*' バッファにコピーして、各々のS式をそのバッファ で評価してやらなければならない。そして (print-elements-recursively animals) を評価する際は C-u C-x C-e を使う必要がある。でないと、 Lisp インタプリタは結果をエコー領域に一行分だけしか表示してくれない。

また、カーソルを print-elements-recursively 関数の最後の閉じ括弧 の直後の、コメントの手前の位置に持っていって評価しないといけない。そうし ないと、Lisp インタプリタはコメントまで評価しようとしてしまう。

 
(setq animals '(giraffe gazelle lion tiger))

(defun print-elements-recursively (list)
  "Print each element of LIST on a line of its own.
Uses recursion."
  (print (car list))                  ; 本体
  (if list                            ; do-again-test
      (print-elements-recursively     ; 再帰呼び出し
       (cdr list))))                  ; next-step-expression

(print-elements-recursively animals)

print-elements-recursively 関数は最初にリストの一番目の引数、即ち、 リストの CAR を表示する。そしてもしリストが空でなければ、この関数内 で自分自身を呼び出す。ただし引数としては、全体のリストではなく二番目以降 の要素からなるリスト、即ち、そのリストの CDR を渡す。

この時の評価では、この関数は引数として受け取ったリストの最初の要素 (これ は元々のリストでは二番目の要素であたる) を表示する。そして if 式 が評価され、それが真であれば、この関数は自分自身を今回受け取った引数の CDR (これは、元々のリストの CDR の CDR である) を引数とし て再度自分自身を呼び出す。

関数が自分自身を呼び出す度に、引数として渡されるリストは元々のリストに比べ て短くなっていき、結果として最後には空リストとともに呼び出すことになる。 print 関数は空リストを nil として表示する。そして次に条件 分岐の部分が list の値をテストする。listnil な ので、if 式は偽を返し、もはや then-part は実行しない。関数全体と しては nil が返されるので、この関数を評価すると最後に二回 nil が現れる。

(print-elements-recursively animals)`*scratch*' バッファ で評価すると、次のような結果が表示されるはずだ。

 
giraffe

gazelle

lion

tiger

nil
nil

(最初の nil は空リストの値が表示されたものであり、二番目の nil は関数全体の値である。)


11.2.2 カウンタの代わりに再帰を使う

前節で説明した triangle 関数もまた再帰的に書ける。これは次のよう になる。

 
(defun triangle-recursively (number)
  "Return the sum of the numbers 1 through NUMBER inclusive.
Uses recursion."
  (if (= number 1)                    ; do-again-test
      1                               ; then-part
    (+ number                         ; else-part
       (triangle-recursively          ; 再帰呼び出し
        (1- number)))))               ; next-step-expression

(triangle-recursively 7)

これを評価することでこの関数をインストールし、試しに (triangle-recursively 7) を評価してみよう。(カーソルを関数定義の 直後の、コメントの手前の位置に持っていって評価することを忘れずに。)

この関数がどのように動作するかを確かめるため、この関数に1、2、3、4等の様々 な引数を与えた場合に何が起きるかを考えてみよう。

まず引数の値が1だとどうなるか?

この関数では説明文字列の後に if 式がくる。これは number の 値が1かどうかテストするものである。もし1であれば、Emacs は if 式 の then-part を評価する。この場合は関数の値として1が返される。(一行から なる三角形の中には小石は1つしかない。)

では引数の値が2であればどうだろう。この場合は Emacs は if 式の else-part を評価する。

今の場合、else-part は足し算と triangle-recursively の 再帰呼び出し、そしてデクリメントからなっている。具体的には次の通り。

 
(+ number (triangle-recursively (1- number)))

Emacs がこのS式を評価する時は、まず最も内側のS式から評価していき、順に 他の部分を評価していく。詳しく書くと次のようなステップを踏むことになる。

Step 1 最も内側のS式の評価。

今の場合、最も内側のS式は (1- number) なので、Emacs は number を2から1に減らす。

Step 2 triangle-recursively 関数の評価。

この関数が自分自身の内部に含まれていることとは関係なく、Emacs は Step 1 の結果をこの関数の引数として渡す。

今の場合、Emacs は triangle-recursively を引数1とともに評価する。 さっき見たように、この場合この関数は1を返す。

Step 3 number の値の評価。

ここでいう変数 number+ で始まるリストの二番目の要素。 その値は2である。

Step 4 + 式の評価。

+ 式は二つの引数を受け取る。一つ目は number を評価して返さ れた値 (Step 3) であり、二つ目は triangle-recursively を評価して 返された値 (Step 2) である。

足し算の結果は2と1の和であり、3が返される。これは正しい結果である。 二行からなる三角形の中には小石は3個含まれる。

引数3の場合  


引数3の場合

triangle-recursively が引数3とともに呼び出されたとする。

Step 1 do-again-test の評価。

まずは if 式が評価される。これは do-again-test であり、偽が返され る。従って、if 式の else-part が評価される。(この例では、テストの 結果が真の時ではなく偽の時に自分自身を再帰呼び出しすることに注意しよう。)

Step 2 else-part のもっとも内側のS式の評価。

else-part の最も内側のS式が評価され、3が2にデクリメントされる。これ が next-step-expression である。

Step 3 triangle-recursively 関数の評価。

数値2が triangle-recursively 関数に渡される。

前節で説明した通り、triangle-recursively は引数2とともに評価さ れると3を返すのであった。

Step 4 足し算の評価。

足し算の式では3がこの時の number の値3に加えられる。

全体として、この関数が返す値は6になる。

以上で triangle-recursively に引数3を与えるとどうなるかが分った。 もはや引数が4の場合に何が起きるかは明らかであろう。次のような感じだ。

再帰呼び出しで

 
(triangle-recursively (1- 4))

が評価され、結果として

 
(triangle-recursively 3)

の値を返す。これは6であり、この値に三行目の足し算で4が加えられる。

全体としてこの関数が返す値は10になる。

triangle-recursively が評価されるごとに、より小さい引数とともに自 分自身を評価し、その状況が、引数がもはや再帰呼び出しを起こさない程小さく なるまで続けられるというわけである。


11.2.3 cond を使った再帰の例

以前説明したバージョンの triangle-recursively は特殊形式 if を用いて書かれていた。これは cond と呼ばれる特殊形式を 用いても書くことが出来る。特殊形式 cond の名前は conditional という単語の短縮形から来ている。

特殊形式 cond は Emacs Lisp では if ほど頻繁に使われている とは言えないが、ここで説明する価値がある程度には使われている。

cond 式のテンプレートは次の通りである。

 
(cond
 本体...)

ここで 本体 はリストの列である。

本体の中身をもっと詳しく書くと次のような感じになる。

 
(cond
 ((最初の真偽テスト 最初の結果部)
  (二番目の 二番目の結果部)
  (三番目の 三番目の結果部)
  ...)

Lisp インタプリタが cond 式を評価する時は、まず cond の本 体のS式の列の最初のS式の最初の要素 (CAR つまり真偽テストの部分) から評価する。

もし、真偽テストが nil を返したなら、その式の残りの部分 (これを結 果部 (consequent) と呼ぼう) はスキップされて、次のS式の真偽テストが評価 される。こうして、もしあるS式で真偽テストが nil 以外の値を返した なら、そのS式の結果部が評価される。結果部は一つでも複数でも構わない。複 数の場合は各々の式が順に評価されていき、最後の値が返される。もしそのS式 が結果部を持たなければ、真偽テストの結果が返される。(訳註:そして、真偽 テストが真であったS式以降は無視される。)

どのS式の真偽テストも偽を返した場合は cond 式は nil を返 す。

cond を使って書くと、triangle 関数は次のようになる。

 
(defun triangle-using-cond (number)
  (cond ((<= number 0) 0)
        ((= number 1) 1)
        ((> number 1)
         (+ number (triangle-using-cond (1- number))))))

この例では、cond は number が0以下の場合は0を返し、1の場合は1を返 し、1より大きい場合は (+ number (triangle-using-cond (1- number))) が評価される。


11.3 ループについての練習問題


[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by Matsuda Shigeki on April, 10 2002 using texi2html