リストに対する再帰型の計算を思いだそう。
(defun 再帰 (list) (cond ((null list) 終了時の値) (t (返り値の計算 (car list) (再帰 (cdr list))))))このような短い計算でも、問題はさらに終了時の計算と返り値の 計算に分割されている。この問題を末尾再帰的に記述すると、また、 異なる問題の分割になる。
(defun 再帰 (list 今の状態) (cond ((null list) 今の状態) (t (let ((次の状態 (次の状態の計算 (car list) 今の状態)) (再帰 (cdr list) 次の状態))))))この場合、末尾再帰関数は一種の状態遷移機械になっている。この 計算を途中で止めるためには、funcall, #', lambda を使うのであった。
(defun 再帰 (list 今の状態) (cond ((null list) 今の状態) (t (let ((次の状態 (次の状態の計算 (car list) 今の状態)) #'(lambda () (再帰 (cdr list) 次の状態)))))))defstruct は、データベースのようなレコードを定義する。しかし、 たくさんのデータを定義しても、それは表にはならない。ところが、 個々のデータを区別するID(Identifier, 識別子)が付いている。これに より同じレコードを複数用意したり、レコードの中にレコードを入れたり、 インデックスや木を作ったりできる。一般にデータベースにIDを入れると、 より強力な表現力をもつ。しかし、その強力なデータベースに対する 総合的で包括的な質問をおこなう方法はない。
(sum-of (integer-list 50 0) 0) のような例では、二つの再帰関数が 交互に動く。これは、状態をもつ関数が相互に協調した動作をおこなう 方法である。sum-of の引き数は単なる値ではなく、むしろ接続すべき プロセスを引き渡している。実際、複数のプロセスを作っておいて、 それを後で組み合わせるというのが良くおこなわれる。その結合は、
これらの関数をつなげて大きなプログラムを作っていく方法の逆を 考える。それは、プログラムを分割していく方法に相当する。問題を 分析して、より小さい要素に分割していく。その時に、どううまく分割するか どうかを考えなくてはならない。
ここで出てきた合成/分割の方法を、Object Life Cycleという手法では 以下の3種のモデルに対応させている。
一方、オブジェクトの考えは、プログラミングの視点からは以下のような 特徴がある。
Common LISPには、CLOS(Common Lisp Object System)というのが用意されている が、ここしばらくは、defustruct に関数を入れるような方法を使おう。
(defstruct btree (key nil) (value nil) (left nil) (right nil) (insert #'btree-insert-method) (delete #'btree-delete-method) ) (defmacro send (obj message &rest args) `(funcall (,message ,obj) ,obj . ,args)) (defun btree-insert-method (self args) ... )そして、これを、
(setf obj (make-btree)) (send obj btree-insert '(key value))などと使うことにしよう。もちろん、もっといろいろな実現方法が存在する。 PCLOS などは CLOS をCommon LISPに変換するシステムになっている。
ここで、defstruct で作ったデータをオブジェクトまたはインスタ ンス(実体)、そこに出てくる要素をオブジェクトの属性またはイン スタンス変数という。中に出てくる関数はメソッドと呼ばれる。
もし、本当のオブジェクト指向言語ならば、btree-insert などと指定する のはおかしい。何故ならobjがいったい何者なのかは、objが知っている はずだからである。それを明示的にselfというインスタンス変数(もちろん 特殊な変数だ)で表すことも多い。実際、selfの存在がオブジェクト 指向言語の資格であるという人もいる。
このdefstructを使ってたくさんのbtreeオブジェクトを作ることが
できる。このようなものをクラス(類)という。オブジェクトはクラスから
作られると覚えよう。
オブジェクトの間の関係は、
オブジェクトのIDは、オブジェクトへのポインタあるいはユニーク な識別子であり、データベースでのキーと同じ役目を果たす。しか し、オブジェクトのIDは普通はプログラマが直接指定することはな い。
オブジェクトの中身を直接いじることなく、オブジェクトIDを 通してオブジェクトにアクセスすることにより、インタフェースと 実装の分離を安全におこなうことができる。しかし、 その分、実行は遅くなる。オブジェクトIDからメッソドを探す という操作が必要だからである。もし、メソッドを呼び出す時に、 そのオブジェクトが何かを指定してやれば、そういうデメリットはない。 C++などは、そういう発想で作られている。
継承関係は、あるオブジェクトからすべての属性を引き継ぎ、さらに 何んらかの属性をつけ加えることを意味する。実際には、属性だけで なく、 オブジェクトの振る舞いもなんからの形で引き継いでいることを さす。ただし、今の所を、属性以外の条件はそれほど明確ではない。 継承するオブジェクトのプログラム理論的な意味をすべて引き継ぐ という考え方もあるが、それを厳密に行うのは実はかなりやっかい なことだし、実用的でもない。
データベースといっても、オブジェクトには一般的な検索方法は 存在しない。もしデータベースが欲しければ、 それは自分で実装しなくてはならない。
(defstruct signal (light 'red) (next 'singal-next-method)) (defun singal-next-method (self) (let ((now (signal-light self))) (cond ((eq now 'green) (setf (signal-light self) 'yellow)) ((eq now 'yellow) (setf (signal-light self) 'red)) ((eq now 'red) (setf (signal-light self) 'green)) (t 'error)))) (setf sig (make-signal)) (send sig next)これを図に書くと以下のようになる。
ここでは、長方体の中を飛び回る分子と、その化学反応を3Dで シミュレーションすることを考えよう。 まず、オブジェクトと、その関係を考えてみよう。
これらを図にしてメールで提出せよ。
状態モデルについては来週また考えることにしよう。