Object Classifier入門

目次

  1. Object Classifierとは
  2. 簡単な例
  3. やや複雑な例
  4. classifier定義の構文と意味

Object Classifierとは

LispやSmalltalk、あるいは、アプリケーションで、動的にobjectを扱えるシステム において、その実行空間に存在するobjectを、ブラウズする機能は必ず必要になる。 たとえば、inspectorは、個々のobjectをブラウズするツールとして不可欠なものだ。 Object Classifierは、一群のobjectを、ユーザが定義した分類条件に応じて階層化 してみせるtoolである。 階層化というのは、ファイルシステムのディレクトリ階層のように、分類条件によって objectを位置づける場所を階層化することを意味している。 たとえば、本のobjectがあったとき、まず本のテーマで分類し、その中を著者で分類し、 最後に、書かれた言語で分類する、という三段階の階層構造などを指定するわけである。 Object Classifierは、object自体を変更するものでなく、分類した姿を提示する だけのものである。 今回の実装は、Lispで行ったが、objectに対するreflectionの機能があれば、どのような 言語でも実装可能である。

簡単な例

たとえば、上にあげた本のobjectを分類することを考えてみよう。
(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)
   )
  )

(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
   (thema ()
             (lambda (x) (doc-thema x))
             (lambda (x) (slot-exists-boundp x 'thema)))
   (author (thema)
          (lambda (x) (slot-value x 'author))
          (lambda (x) (slot-exists-boundp x 'author) ))
   (lang (author)
         (lambda (x) (doc-lang x) )
         (lambda (x) (slot-exists-boundp x 'lang)))
   )

定義の説明

簡単に説明しよう。
まず、defclassifierによって、classifierと呼ばれる、分類の定義がdefineされる。 このclassifierの定義はclassになっていて、そのインスタンスをmake-classifierで 作成すると、そのインスタンスは、objectを分類する入れ物として働く。
同じclassifierの定義に基づいて、中に入れるobjectの異なる、いろいろな入れ物を 作ることができる

simple-classifier1 というのは、classifierの名前であり、make-classifierの パタメタとして指定する。

その後に続くものは、分類の各単位の定義となる。 その中の最初のもの

   (category ()
             (lambda (x) (doc-thema x))
             (lambda (x) (slot-exists-boundp x 'thema)))
について説明しよう。 このひとまとまりのものを category と呼ぶ。
objectは、このcategoryの定義にしたがって分類される。

themaは、このcategoryの名前である。docのslotのthemaに着目しているので、 このような名前を付けた。

次の()は、このcategoryの親のcategoryの名前である。ここでは、親はないので ()になっている。
classifierには、すべてのcategoryのrootとなるcategoryがただひとつ存在する。 それは t category と呼ばれ、親のない categoryは、t categoryの直接の子供だと 解釈される。
ユーザーが t categoryを定義することはできない。

次の関数は、後で説明するので、ひとつ飛ばして、最後の関数 (lambda (x) (slot-exists-boundp x 'thema)) を説明しよう。
これは、この category に属するobjectが満たさなくてはならない条件(constraint) を示している。

1引数の関数であり、その引数には object が渡され、値としてtか nilを返すことが必要である。(nil以外はすべてtとみなすのだけど)

ここでは、themaというslotが存在し、値がbindされていることが条件になっている

さて、ひとつ飛ばした関数 (lambda (x) (doc-thema x))の説明をするためには、 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をとり、値としてatomや文字列を 返す関数であることが必要である。(たぶんどんなobjectでもいいはず)

t categoryのpartitionは唯一つ存在し、それはt representationまたは t partitionと呼ばれる。これは、すべてのpartitionのrootになる。

実行例

まず、classifierのインスタンスを作成する。
(setf bug1 (make-classifier 'simple-classifier1))
また、次のようなdocumentのinstanceがあるとしよう。
(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")
                           )))
このとき、つぎのようにして、bug1にobjectを登録する。
(put-object* bug1 doc)
登録された様子を見るためのutility showを使うと、次のようにみえる
(show bug1 'display)

[T]/
 [Novel]/
  [tanaka]/
   [JAPAN]/
    {Quick Start by tanaka#Novel#JAPAN#("Object Oriented" "Lisp")}
 [mathematics]/
  [omura]/
   [JAPAN]/
    {user's manual by omura#mathematics#JAPAN#("Object Oriented" "Lisp")}
 [Computer Science]/
  [omura]/
   [JAPAN]/
    {reference manual by omura#Computer Science#JAPAN#("Object Oriented" "Lisp")}

NIL
[]で囲まれた名前が partition であり、{}で囲まれた部分は、method display によって 表示されたdocumentの情報である。

やや複雑な例

テスト用のファイルがあるので、それをみてください。

classifier定義の構文と意味

例でだいたい分かると思うので、省略。

注意事項

  1. categoryの定義は、cyclicに書く事ができない。当然、自分自身を親に指定することもできない。
  2. categoryの定義で、親が複数あるとき、親の中の二つのcategoryに、親子関係があってはならない。 つまり、aがbの祖先であることをa>bと書くとして、category c の親として(a b)が指定されている場合、 aとはいえ、親が複数指定された場合のsemanticsがうまく定義できないので、この機能を使うことは 禁止する。