再帰の弱点

 ここから先のコードはSchemeで。さっきは再帰いいよね!という話を書いたので、再帰よくないよね!という話も書いておきたいと思う。再帰のよくないところは、名前を付けないといけないところだと思う。例えば、階乗計算を考えると

(define (fact n)
  (if (= n 1)
    1
    (* n (fact (- n 1)))))

と書ける。これはまぁ問題ないのだけど、これじゃあ大きな数を計算するときにスタックが溢れちゃうので、末尾呼び出しにしたい。というわけで、書き直してみる。

(define (fact n answer)
  (if (= n 1)
    answer
    ((fact (- n 1) (* answer n)))))

直接日記インターフェースで書いてるので括弧の対応が取れてるのか自信が無いが、それはさておき。このfactは呼び出す際に(fact n 1)と、answerのところに1を指定してやらねばならない。それはちょっとひどい話だ。というわけで、こんな関数は書き直すべきなのだが、その際には以下のような2つの形が考えられる。

(define (fact1 n)
   (fact-rec n 1))

(define (fact1-rec n answer)
  (if (= n 1)
    answer
    (fact (- n 1) (* answer n))))

(define (fact2 n)
  (let inner-loop ((n n)
		   (answer 1))
       (if (= n 1)
	   answer
	 (inner-loop (- n 1) (* answer n)))))

 fact1は、ラッパーを作って中では別の関数を適切に呼び出す、という事をしているだけなのだが、そのためにその呼び出すための関数の名前を決めるという作業が発生している。ここでは単純に-recという名前を付け足した。これは割り切りとしてアリではあるが、やはり好ましいと言えるものでは無い。
 fact2はnamed letを使ってループを作っているが、これはinner-loopという名前が適切か、という問題がまず一点。次に、(n n)の部分。これは、非常に気持ち悪いのだが、名前を付け変えるのも大変だ。そもそもこのnはユーザからfactに与えられた引数、という程度の意味しか持っておらず、その意味では左のnも右のnも同じものなのであるが、ループを回ると左のnと右のnでは値が違う(より正確には、ループの中と外では値が違う、とでも書くべきだろうか。これでもまだ正確な表現ではないけれど。)ようになってくるわけで、値が違うものにはやはり字面の上でも別の名前を付けておきたいという心持ちもある。
 事程左様に、再帰を使ったコードというのは命名という点からすると悩ましい部分がある。これはforとかつかってなんとなくループを書いていたときにはなかった悩みだ。あちらを立てればこちらが立たず。なんだか、もぐらたたきをしているようだ。
 なんでこんな事を今さらいちいち書いているのかというと、和田研フォントキットを読んでいると、なんとなくそういう気分になってくるからだ。なぜ和田研フォントキットはこのような気持ちを呼び覚ますのか、それに関する考察は、次回までの宿題としたい。