HOME | 9. 入出力 | もうひとつの Scheme 入門 | 11. 文字、文字列 | 書き込む |
ここまで代入について説明しなかったのは、 代入抜きのプログラミングに慣れていだだきたかったのと、 代入にはそれなりの弊害があるからです。 代入の弊害については、 SICP: 3.1 Assignment and Local State や なぜ関数プログラミングは重要か を見てください。
Scheme は基本的に関数型プログラミング言語なので、基本的には代入を用いないでプログラムを書くことができます。 しかし、代入を用いたほうがかえって簡潔に書ける場合もあり、 内部状態や継続を利用する時は代入を使う必要があります。
R5RS に定義されている代入ステートメントには set!, set-car!, set-cdr!, string-set!, vector-set! などがあります。また、そのほかに処理系依存の代入ステートメントがあります。 scheme では、代入などの破壊的なステートメントの名前にはプログラマーの注意を促すために ! が付きます。
次のように使います。
(define var 1) (set! var (* var 10)) var ⇒ 10 (let ((i 1)) (set! i (+ i 3)) i) ⇒ 4
変数の有効範囲を限定する式には let 式、lambda 式および後で説明する letrec 式 などがあります。lambda 式の引数は、そのlambda 式内でのみ有効です。
実は、let 式は下に示すように lambda 式で置き換えることができます。
(let ((p1 v1) (p2 v2) ...) body) ⇔ ((lambda (p1 p2 ...) body) v1 v2 ...)
(define bank-account (let ((amount 1000)) (lambda (n) (set! amount (+ amount n)) amount)))残高 amount に (+ amount n) を代入しています。
> (bank-account 2000) ;2000 円預け入れ 3000 > (bank-account -2500) ;2500 円引き出し 500
Scheme は手続きを返す手続きを書くことができるので、
銀行口座を作る関数を書くこともできます。
この例を見ると、関数型言語を使ってオブジェクト指向にするのは簡単であることがわかると思います。
実際、あとほんの少し手を加えればオブジェクト指向になります。
(define (make-bank-account amount) (lambda (n) (set! amount (+ amount n)) amount))
> (define yamada-bank-account (make-bank-account 1000)) ; 山田さんが 1000 円預金して銀行口座を作る。 yamada-bank-account > (yamada-bank-account 5000) ; 5000 円預け入れる 6000 > (yamada-bank-account -5500) ; 5500 円引き出す 500 > (define saito-bank-account (make-bank-account 10000)) ; 斉藤さんが 10000 円預金して銀行口座を作る。 saito-bank-account > (saito-bank-account -7000) ; 7000 円引き出す 3000 > (saito-bank-account 30000) ; 30000 円預け入れる 33000
> (let ((ls (list 1 2 3))) (set-car! ls 0) ls) (0 2 3) > (let ((ls (list 1 2 3))) (set-cdr! ls '(20 30)) ls) (1 20 30)
Queue の最後に要素 (item 4) を追加するには、Queue の最後のコンスセル ((cdr cons-cell-top) で直接アクセスできます) の cdr 部のポインターを、 car 部が item 4, cdr 部が '() のコンスセルにします。 その後、cons-cell-top の cdr 部を、そのコンスセルへのポインターにします。(図 2)
一方、先頭の要素を取り出して、Queue からそれを取り除くには、先頭の要素をまず、局所変数に 保存し、その後、 cons-cell-top の car 部を リストの2番目のコンスセルに移します(図 3)。
[code 1] に R5RS 版の Queue を実装したコードを示します。 [code 1] の enqueue! は queue の最後の obj を追加した Queue を返す関数、 dequeue! は queue から最初の要素を取り除き、取り除いた最初の要素を返す関数です。
MzScheme では、通常の pair や list は書き換えられないので、別に書き換えられる pair, list である mutable-pair が用意されています。 通常のリストや pair が破壊的に操作できるとバグの原因になるので、両者を区別するのはいいアイデアだと思います。 だた、RnRS の仕様とは外れるので、ほかの処理系との互換性に問題が生じます。 [code 1a] に MzScheme での Queue の実装を示します。
[code 1] (RnRS 版)
(define (make-queue) (cons '() '())) (define (enqueue! queue obj) (let ((lobj (cons obj '()))) (if (null? (car queue)) (begin (set-car! queue lobj) (set-cdr! queue lobj)) (begin (set-cdr! (cdr queue) lobj) (set-cdr! queue lobj))) (car queue))) (define (dequeue! queue) (let ((obj (car (car queue)))) (set-car! queue (cdr (car queue))) obj))
[code 1a] (MzScheme 版)
(require scheme/mpair) (define (make-queue) (mcons '() '())) (define (enqueue! queue obj) (let ((lobj (mcons obj '()))) (if (null? (mcar queue)) (begin (set-mcar! queue lobj) (set-mcdr! queue lobj)) (begin (set-mcdr! (mcdr queue) lobj) (set-mcdr! queue lobj))) (mcar queue))) (define (dequeue! queue) (let ((obj (mcar (mcar queue)))) (set-mcar! queue (mcdr (mcar queue))) obj))
> (define q (make-queue)) > (enqueue! q 'a) {a} > (enqueue! q 'b) {a b} > (enqueue! q 'c) {a b c} > (dequeue! q) a >
また、今回は変数のスコープについても説明しました。
次回から数回は、Scheme で扱えるデータ型について解説します。
(define (make-bank-account amount) (lambda (n) (let ((m (+ amount n))) (if (negative? m) 'error (begin (set! amount m) amount)))))
HOME | 9. 入出力 | もうひとつの Scheme 入門 | 11. 文字、文字列 | 書き込む |