クロージャとオブジェクト
関数本体の定義とそれを評価するための環境を合わせてクロージャと呼ぶ. クロージャの概念を用いれば,オブジェクト指向プログラミングの基本要素であるオブジェクトを作成することができる.
オブジェクトとは,簡単に言えば,メンバ変数(フィールド)といわれるデータとそれを操作するためのメンバ関数(メソッド)をまとめた部品のようなものである. 各フィールドのスコープはオブジェクト内に局所化され,それらにアクセスするためには必ずメソッドを用いることになる. つまりオブジェクト内部のデータは予め指定された方法でのみ操作され,プログラムの他の部分から予期せぬ形で影響を受けることがない. こうすることによって,オブジェクトの内部構造がブラックボックス化され,オブジェクトを,ある機能を提供するプログラムの抽象的な部品として,その詳細な実現方法に左右されることなく利用することができるようになる. ここでは環境モデルを用いて,関数がオブジェクトとして働くことをみる.
以下の関数open-accountは,引数balanceをとり,lambda式(関数)を返す.
(define (open-account balance) (lambda (method amount) (cond ((eq? method 'withdraw) (if (>= balance amount) (set! balance (- balance amount)) (display "insufficient funds ")) ; 画面に文字列を表示 balance) ((eq? method 'deposit) (set! balance (+ balance amount)) balance) (#t (display "no such method : ") ; 画面に文字列を表示 (display method) (newline)))))ここで,
(define my-acc (open-account 1000))と定義したときの環境は以下のようになる. 関数open-accountの引数に1000を与えて呼び出すと,仮引数balanceに実引数1000をバインドしたフレームが新たに作成される. これによって定義される環境env1で,関数open-accountの本体が評価される. 評価されるのはlambda式であり,クロージャが生成される. 本体は,もちろん関数open-account内で与えられているlambda式であり,環境は,このlambda式が評価された環境,すなわち環境env1である. 最後にdefineによって,このクロージャがmy-accという名前でグローバルフレームに登録される.
my-accは,2つの引数method,amountをとる関数として呼び出すことができる.
> (my-acc 'withdraw 200) ;; balance 1000 --> 800 800 > (my-acc 'deposit 500) ;; balance 800 --> 1500 1300 > (my-acc 'withdraw 2000) ;; balance 1300 < 2000 insufficient funds 1300例えば,最初の式(my-acc 'withdraw 200)を評価するときの環境は以下のようになる. バインドされた引数のフレームの上位環境はグローバルフレームではなく,環境env1つまりmy-accの本体が評価されたときの環境であることに注意. 環境env2において,以下を評価すると,
(cond ((eq? method 'withdraw) (if (>= balance amount) (set! balance (- balance amount)) (display "insufficient funds ")) balance)変数balanceは環境env1から得られ(1000),set!によってその値が変更される(800). 続いて(my-acc 'deposit 500)を呼び出すと,変数balanceは,800+500 = 1300に変更される.
このようにmy-accの定義の時点で作られたフレームは,関数open-accountの呼出し後も捨てられずに残り,それを指す環境があれば,その中の変数の値を調べたり変更したりできる. またさらに,
> (define her-acc (open-account 2000))と定義すると,my-accとは全く独立なクロージャが新たに生成される. my-accのbalanceとher-accのbalanceは,もちろん別々のフレームに格納される.
ところで,この関数open-account(銀行口座の開設)は,balance(残高)というフィールドとamount(金額)を引数としたwithdraw(引出し),deposit(入金)の2つのメソッドを持つオブジェクトを生成するものである. 生成されたオブジェクト(my-acc,her-acc)のデータは2つのメソッドを通してのみ変更可能であり,外部(グローバル環境)から直接参照することはできない.