2012年4月23日月曜日

Scheme第5歩


ループと再帰の話。
まず、他の言語によくあるforやwhileのようなものはSchemeにはない。なので、ループに相当するコードは再帰で書く。それだと10000回ループしたらどんだけスタックを食い潰すんだ? …と、手続き型言語に慣れたプログラマなら心配するところけれど、Schemeは末尾再帰をループにすることを言語仕様で定めているそうな。

再帰であろうとなかろうと、プロシージャの呼び出し方には違いはないが、問題はプロシージャの定義の仕方。
(define sum (lambda (x) (if (= x 0) 0 (+ x (sum (- x 1))))))
このようにグローバルなら別に問題ないのだが、問題はローカルな再帰プロシージャを定義したい場合。letにしてもlet*にしても、ローカル変数の初期値を評価するところからは、そのローカル変数自信が見えない。そこでletrecを使う。
(letrec
  ((sum (lambda (x) (if (= x 0) 0 (+ x (sum (- x 1)))))))
  (display (sum 10))
  (newline)
)
letrecの第1引数はletと同じフォーマットに見えるが、そこで導入されたローカル変数を評価するときは、letと違ってローカル変数を参照できる。
(letrec ((c (+ b 1)) (b (+a 1)) (a 1)) (display c))
この例ではc, b, aという定義の順番にとらわれることなく
a = 1
b = 2
c = 3
となる。

ややこしいのが名前付きlet。
(let loop ((i 10) ...)

(letrec ((loop (lambda (i) ...))) (loop 10))
と同じ。例えば1から10まで表示する場合、名前付きletを使うと
(let disp ((i 1)) (display i) (newline) (unless (= i 10) (disp (+ i 1))))
と書ける。

改めてletを見てみると、letの中でのみ有効な名前付きのプロシージャを定義して呼び出すことこそ、letの本質のように思えてきた。そう考えると、ローカル変数はあくまでプロシージャの仮引数機能で作っているだけのように見える。

0 件のコメント:

コメントを投稿