ループと再帰の話。
まず、他の言語によくあるforやwhileのようなものはSchemeにはない。なので、ループに相当するコードは再帰で書く。それだと10000回ループしたらどんだけスタックを食い潰すんだ? …と、手続き型言語に慣れたプログラマなら心配するところけれど、Schemeは末尾再帰をループにすることを言語仕様で定めているそうな。
再帰であろうとなかろうと、プロシージャの呼び出し方には違いはないが、問題はプロシージャの定義の仕方。
(define sum (lambda (x) (if (= x 0) 0 (+ x (sum (- x 1))))))このようにグローバルなら別に問題ないのだが、問題はローカルな再帰プロシージャを定義したい場合。letにしてもlet*にしても、ローカル変数の初期値を評価するところからは、そのローカル変数自信が見えない。そこでletrecを使う。
(letrecletrecの第1引数はletと同じフォーマットに見えるが、そこで導入されたローカル変数を評価するときは、letと違ってローカル変数を参照できる。
((sum (lambda (x) (if (= x 0) 0 (+ x (sum (- x 1)))))))
(display (sum 10))
(newline)
)
(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 件のコメント:
コメントを投稿