再帰でループします。

好きな数字を心に思ってください。その数字に3を足します。さらに2をかけます。今度は4を足してください。2で割ります。最後に、最初に思った数字を引いてください。


さて。好きな数字を思ってもらいました。さらに計算をしたので、その結果は誰にも分かりません。それでは、最後に出た数字を、しっかりと思い浮かべてください。しっかりと。


分かりました。


その数字は、「5」です。


というわけで、サイキックなid:shunsukです。今日は、再帰ックなお話をします。再帰を使うと、繰り返しの処理を行うことができます。それが、最初は難しい。Schemeの最初のつまずきポイントだと思います。つまずき聡です。


nの階乗を求めるプログラムです。fact関数を定義していますが、その中で自分自身(fact関数)を読んでいます。これを再帰と言います。

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

(fact 5)
;=> 120


(* 5 (* 4 (* 3 (* 2 1))))がイメージできますか?難しいですね。Schemeにはdoという繰り返し構文もあります。

(define (fact n)
  (do ((m n (- m 1)) (x n (* x (- m 1))))
    ((= m 1) x)))

(fact 5)
;=> 120


mをnで初期化して、処理ごとに(- m 1)しています。また、xをnで初期化して、(* x (- m 1))しています。そして、(= m 1)になったらxを返しています。doの方が使いづらいでしょ?そこで、Schemeでは再帰が多用されます。


もう一度、再帰のプログラムに戻ってみましょう。

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

(fact 5)
;=> 120


関数の中で関数を呼ぶと、関数呼び出しの履歴が積み上げられて(スタックされて)いきます。そうすると、パフォーマンスが低下するのです。ここで、fact関数で最後に評価されるのは、nと(fact (- n 1))との掛け算です。実は、最後に再帰関数呼び出しを行うと、スタックされずに、単純にジャンプするようになる裏ワザがあるのです。と言っても、誰もが知っている裏ワザなので、むしろ表ワザです。


下のコードでは、fact-iter関数の最後がfact-iter関数の呼び出しになっています。

(define (fact n)
  (fact-iter n n))

(define (fact-iter n x)
  (if (= n 1)
      x
      (let ((next-n (- n 1)))
	(fact-iter next-n (* x next-n)))))

(fact 5)
;=> 120


これにより、効率よく処理を行うことができるようになります。これを、末尾再帰と言います。ラーメン、つけ麺、末尾再帰


2つの関数に別れてしまいましたが、1つにまとめることもできます。loop構文を使います。mにnの値をセット。xにnの値をセット。

(define (fact n)
  (let loop((m n) (x n))
    (if (= m 1)                    
        x
        (let ((next-m (- m 1)))
          (loop next-m (* x next-m))))))

(fact 5)
;=> 120


下のような書き方もできます。letrecはletのパワーアップバージョンです。iterの中でiterを呼べるのがポイント。

(define (fact n)
  (letrec ((iter (lambda (m x)
		   (if (= m 1)
		       x
		       (let ((next-m (- m 1)))
			 (iter next-m (* x next-m)))))))
    (iter n n)))

(fact 5)
;=> 120


というカンジで、再帰のプログラムを載せてみました。解ってる人にはカンタンすぎて、解らない人にはサッパリ解らない。愛しいけれど憎いヤツです。


9LISP 003で、再帰までやるかどうか分かりませんが、もっと解りやすい解説ができるように勉強してきますね。9LISP 003で、再帰までやるかどうか分かりませんが、もっと解りやすい解説ができるように勉強してきますね。繰り返し処理してみました。