たとえば、LISPやSmalltalkのinspectorは、個々のobjectをブラウズするツールとして不可欠なものだ。
また、WindowsのExploreも、ファイルやディレクトリをオブジェクトと考えたときの
ひとつのブラウザの形態を示していると考えられる。
このような、Object Browserを考えるための基礎となるメカニズムとして、Object Classifierを考案した。
Object Classifierは、一群のobjectを、ユーザが定義した分類条件に応じて階層化 してみせる装置である。
たとえば、本のobjectがあったとき、まず本のテーマで分類し、その中を著者で分類し、 最後に、書かれた言語で分類する、などといった階層構造を指定すると、それにしたがって本のobjectを 分類してくれるものである。
Object Browserは、Classiifierによって分類されたオブジェクトを表示したり操作したり するためのユーザインターフェイスにすぎない。
(defclass document () ((title :initarg :title :reader doc-title) (lang :initarg :lang :reader doc-lang) (author :initarg :author :reader doc-author) (thema :initarg :thema :reader doc-thema) (keyword :initarg :keyword :reader doc-keyword :initform nil) (pub-year :initarg :pub-year :reader doc-pub-year :initform 1997) ) )これはLISPのオブジェクト指向言語CLOSによる表現であり、 クラス document を定義している。
documentのインスタンスを表示するためのmethod displayを次のように定義
する。
詳細は、略。
(defmethod display ((doc document)) (format t "{~A by ~A#~A#~A#~S}" (and (slot-boundp doc 'title) (doc-title doc)) (and (slot-boundp doc 'author) (doc-author doc)) (and (slot-boundp doc 'thema) (doc-thema doc)) (and (slot-boundp doc 'lang) (doc-lang doc)) (and (slot-boundp doc 'keyword) (doc-keyword doc)) (and (slot-boundp doc 'pub-year)(doc-pub-year doc)) ) )このように定義された document を、thema, author, lang の順に分類するための 定義は次のようになる。
(defclassifier simple-classifier1 nil (thema (t) (lambda (obj part) (doc-thema obj)) (lambda (obj part) (slot-exists-boundp obj 'thema))) (author (thema) (lambda (obj part) (slot-value obj 'author)) (lambda (obj part) (slot-exists-boundp obj 'author) )) (lang (author) (lambda (obj part) (doc-lang obj) ) (lambda (obj part) (slot-exists-boundp obj 'lang))) )
classifierはclassとして実装されていて、そのインスタンスは、objectを分類する
入れ物として働く。
同じclassifierの定義に基づいて、中に入れるobjectの異なる、いろいろな入れ物を
作ることができる
この例では、simple-classifier1 という名前のclassifierを定義している。
名前の次の nil は、今は無視しよう。
その後に続くものが、実際の分類の定義となる。
最初の
(thema () (lambda (obj part) (doc-thema obj)) (lambda (obj part) (slot-exists-boundp obj 'thema)))について説明しよう。
このひとまとまりのものを category と呼ぶ。
objectは、このcategoryの定義にしたがって分類される。
themaは、このcategoryの名前である。docのslotのthemaに着目しているので、 このような名前を付けた。
次の(t)は、このcategoryの親のcategoryの名前を指定する。ここでは、t という
カテゴリーを親として指定している。
classifierには、すべてのcategoryのrootとなるcategoryがただひとつ存在する。
それは t category と呼ばれる。
ユーザーが t categoryを定義することはできない。
次の関数は、後で説明するので、ひとつ飛ばして、最後の関数
(lambda (obj part) (slot-exists-boundp obj 'thema)を説明しよう。
constraintは2引数の関数であり、その第一引数には分類の対象となる object が渡される。 第二引数については、より高度な機能に関するところで説明しよう。 この関数は、値としてtかnilを返すことが必要である。(nil以外はすべてtとみなす)
ここでは、themaというslotが存在し、値がbindされていることが条件になっている
さて、ひとつ飛ばした関数 (lambda (obj part) (doc-thema obj))の説明をするためには、
representationとpartitionについて説明しなくてはならない。
categoryのconstraintを満たすobjectは、この category に属すると考えられるが、
それらのobjectは、さらに partition という同値類に分類される。
その同値類のひとつひとつにつけられた名前がrepresentationである。
上の本のthemaの例であれば、「小説」であるとか「コンピュータ科学」であるとか
いったthemaの個々の値を、そのrepresentationとすることができ、それに属する
objectの集合がpartitionとなる。
注意すべき点は、partitionは、ひとつのcategoryに対して複数存在し、 個々のpartitionが、categoryの定義に従って、子供のpartitionを持つ ということである。
例えば、authorが「大村」と「田中」と二人いた場合、「小説」の下にもこれらの partitionがつきうるのと同時に、「コンピュータサイエンス」の下にも つくかもしれない。
さて、関数の説明に戻ると、(lambda (x) (doc-thema x))は、representationを 定義する関数であると同時に、同じrepresentationを持つ集合としてそのpartitionも決める。
一引数の関数であり、その引数にはobjectをとり、値として任意のオブジェクト 返す関数であることが必要である。
t categoryのpartitionは唯一つ存在し、それはt representationまたは t partitionと呼ばれる。これは、すべてのpartitionのrootになる。
(setf doc (list (make-instance 'document :title "reference manual" :lang 'japan :author "omura" :thema "Computer Science" :keyword '("Object Oriented" "Lisp") ) (make-instance 'document :title "user's manual" :lang 'japan :author "omura" :thema "mathematics" :keyword '("Object Oriented" "Lisp") ) (make-instance 'document :title "Quick Start" :lang 'japan :author "tanaka" :thema "Novel" :keyword '("Object Oriented" "Lisp") )))このとき、ObjectBrowserを使うと、docをsimple-classifier1で分類した様子が、 次のようにみえる。
leafに表示されている
ここでは、defclassifierの第二引数を用いている。
第二引数で指定した変数(この例ではlevel)は、classiferの各partitionに対応
づけられ、その状態を保持する。
ここでは、ディレクトリ階層の何レベル目かを示す数値を持つようにした。
(defun max-level (part) (if part (loop for parent in (parent* part) maximize (level parent)) 0 ) ) (defun my-dir-name (obj part) (setf (level part) (1+ (max-level part))) (nth (level part)(pathname-directory obj)) ) (defun dir-p (obj part) (nth (1+ (max-level part)) (pathname-directory obj)) ) (defclassifier filelist2 '((level :initform 0 :accessor level)) (directory (t directory) my-dir-name dir-p ) ) (filebrowser 'filelist2 "K:\\ALLEGRO\\lispwork\\testdir\\")
オブジェクトの表現が、生のデータになっているので、次のように表示形式を かえることもできる。
(filebrowser 'filelist2 "K:\\ALLEGRO\\lispwork\\testdir\\" 'file-namestring)