HOME | 6. 局所変数 | もうひとつの Scheme 入門 | 8. 高階関数 | 書き込む |
階乗の計算はよく使われる例です。
[code 1] 階上を求める関数 fact
(define (fact n) (if (= n 1) 1 (* n (fact (- n 1)))))(fact 5) は次のように計算されます。
(fact 5) ⇒ 5 * (fact 4) ⇒ 5 * 4 * (fact 3) ⇒ 5 * 4 * 3 * (fact 2) ⇒ 5 * 4 * 3 * 2 * (fact 1) ⇒ 5 * 4 * 3 * 2 * 1 ⇒ 5 * 4 * 3 * 2 ⇒ 5 * 4 * 6 ⇒ 5 * 24 ⇒ 120(fact 5) は (fact 4) を、(fact 4) は (fact 3) を呼び出し、最後に (fact 1) が呼び出されます。 (fact 5), (fact 4) ,.. (fact 1) はメモリーの別の領域に割り当てられ、(fact i) は (fact (- i 1)) が値を返すまでそこにとどまっていなければならないのでメモリー領域が無駄になり、また、関数呼び出しの オーバーヘッドのため、実行時間も長くかかります。
しかし、再帰には、簡単に繰り返しを表現できるという利点があります。 リストは再帰的に定義されているので再帰関数と相性が良いです。 例えば、リストの要素を全て2倍する関数は次のようにかけます。 リストが空リストのとき空リストを返すようにしないと計算が終わりません。
(define (list*2 ls) (if (null? ls) '() (cons (* 2 (car ls)) (list*2 (cdr ls)))))
[code 2] 末尾再帰版階乗計算関数 fact-tail
(define (fact-tail n) (fact-rec n n)) (define (fact-rec n p) (if (= n 1) p (let ((m (- n 1))) (fact-rec m (* p m)))))fact-tail は次のようにして階乗を計算します。
(fact-tail 5) ⇒ (fact-rec 5 5) ⇒ (fact-rec 4 20) ⇒ (fact-rec 3 60) ⇒ (fact-rec 2 120) ⇒ (fact-rec 1 120) ⇒ 120fact-rec は次に呼び出す関数の値をもとに計算する必要が無いので、役目を終わったらメモリーから消えます。 fact-rec は引数だけが変化して計算が進行しているので実質的にはループと同じです。 Scheme では仕様で、末尾再帰をループに変えることを要求しているので、ループ用の特別な構文なしに、繰り返しを 行うことができます。
名前つき let は Scheme で繰り返しを表す標準的な方法です。
[code 3]
(define (fact-let n) (let loop((n1 n) (p n)) ; 1 (if (= n1 1) p (let ((m (- n1 1))) (loop m (* p m)))))) ; 2
> (ave 1.1 2.3 4.6) 2.6666666666666665 > (ave 3.3 4.7 10.2 20.6 100.1) 27.779999999999994
[code 4]
(define (fact-letrec n)
(letrec ((iter (lambda (n1 p)
(if (= n1 1)
p
(let ((m (- n1 1)))
(iter m (* p m))))))) ; *
(iter n n)))
; * に示すように、
局所関数 iter を iter の定義内で参照することができます。
letrec は局所関数を定義する一般的な方法です。
(do binds (predicate value)
body)
まず、binds 部で変数をバインドし、それから、 predicate でループを
抜け出すかどうか判断し、抜け出すとき value を返します。binds 部の書式は以下の通りです。
[binds] → ((p1 i1 u1) (p2 i2 u2) ... )変数 p1, p2 ... を i1, i2 ... で初期化し、 ループが一順するごとに u1, u2 ... で更新します。
fact を do 式を使って書くと次のようになります。
[code 5]
(define (fact-do n) (do ((n1 n (- n1 1)) (p n (* p (- n1 1)))) ((= n1 1) p)))まず、変数 n1 を n で初期化し、ループが回るごとに 1 を引きます。 変数 p は n で初期化し、ループが回るごとに (n1 - 1) をかけます。 n1 が 1 になったら p を返します。
人によって感じ方は違うと思いますが、かえってわかりにくい感じです。
次回は関数を引数にとる関数(高階関数)について説明します。 高階関数を使いこなすとプログラムが Scheme らしくなります。
; 1 (define (my-length ls) (if (null? ls) 0 (+ 1 (my-length (cdr ls))))) ; 2 (define (my-sum ls) (if (null? ls) 0 (+ (car ls) (my-sum (cdr ls))))) ; 3 (define (remove x ls) (if (null? ls) '() (let ((h (car ls))) ((if (eqv? x h) (lambda (y) y) (lambda (y) (cons h y))) (remove x (cdr ls)))))) ; 4 (define (position x ls) (position-aux x ls 0)) (define (position-aux x ls i) (cond ((null? ls) #f) ((eqv? x (car ls)) i) (else (position-aux x (cdr ls) (inc i)))))
; 1 (define (my-reverse ls) (my-reverse-rec ls ())) (define (my-reverse-rec ls0 ls1) (if (null? ls0) ls1 (my-reverse-rec (cdr ls0) (cons (car ls0) ls1)))) ;------------------- ; 2 (define (my-sum-tail ls) (my-sum-rec ls 0)) (define (my-sum-rec ls n) (if (null? ls) n (my-sum-rec (cdr ls) (+ n (car ls))))) ;-------------------- ; 3 (define (my-string->integer str) (char2int (string->list str) 0)) (define (char2int ls n) (if (null? ls) n (char2int (cdr ls) (+ (- (char->integer (car ls)) 48) (* n 10)))))
; 1a (define (remove x ls) (let loop((ls0 ls) (ls1 ())) (if (null? ls0) (reverse ls1) (loop (cdr ls0) (if (eqv? x (car ls0)) ls1 (cons (car ls0) ls1)))))) ; 1b (define (position x ls) (let loop((ls0 ls) (i 0)) (cond ((null? ls0) #f) ((eqv? x (car ls0)) i) (else (loop (cdr ls0) (inc i)))))) ; 2a (define (my-reverse-let ls) (let loop((ls0 ls) (ls1 ())) (if (null? ls0) ls1 (loop (cdr ls0) (cons (car ls0) ls1))))) ; 2b (define (my-sum-let ls) (let loop((ls0 ls) (n 0)) (if (null? ls0) n (loop (cdr ls0) (+ (car ls0) n))))) ; 2c (define (my-string->integer-let str) (let loop((ls0 (string->list str)) (n 0)) (if (null? ls0) n (loop (cdr ls0) (+ (- (char->integer (car ls0)) 48) (* n 10)))))) ; 3 (define (range n) (let loop((i 0) (ls1 ())) (if (= i n) (reverse ls1) (loop (inc i) (cons i ls1))))) ; 4 (define (ave . ls) (let loop((sum 0) (ls1 ls)) (if (null? ls1) (/ sum (length ls)) (loop (+ sum (car ls1)) (cdr ls1)))))
; 1 (define (my-reverse-letrec ls) (letrec ((iter (lambda (ls0 ls1) (if (null? ls0) ls1 (iter (cdr ls0) (cons (car ls0) ls1)))))) (iter ls ()))) ; 2 (define (my-sum-letrec ls) (letrec ((iter (lambda (ls0 n) (if (null? ls0) n (iter (cdr ls0) (+ (car ls0) n)))))) (iter ls 0))) ; 3 (define (my-string->integer-letrec str) (letrec ((iter (lambda (ls0 n) (if (null? ls0) n (iter (cdr ls0) (+ (- (char->integer (car ls0)) 48) (* n 10))))))) (iter (string->list str) 0)))
; 1 (define (my-reverse-do ls) (do ((ls0 ls (cdr ls0)) (ls1 () (cons (car ls0) ls1))) ((null? ls0) ls1))) ; 2 (define (my-sum-do ls) (do ((ls0 ls (cdr ls0)) (n 0 (+ n (car ls0)))) ((null? ls0) n))) ; 3 (define (my-string->integer-do str) (do ((ls0 (string->list str) (cdr ls0)) (n 0 (+ (- (char->integer (car ls0)) 48) (* n 10)))) ((null? ls0) n)))
HOME | 6. 局所変数 | もうひとつの Scheme 入門 | 8. 高階関数 | 書き込む |