CORBIT/BOOPS: プロトタイプベースオブジェクト指向を実現する拡張ライブラリ

 ひょんなことから見つけた*1のでちょっと遊んでみました。

CORBITとBOOPSについて

 CORBIT (CommonORBIT) はCommon Lisp用のプロトタイプベースのオブジェクト指向ライブラリです。

Package: lang/lisp/oop/non_clos/corbit/

 どうも、Orbitというプロトタイプベースのオブジェクト指向のシステムがあり*2、それをCommon Lisp向けに移植・再実装したものがCORBITであるようです。また、BOOPS (Beginner's Object-Oriented Programming System) はふるまいや仕組みが初学者にもわかりやすいようCORBITのコア要素を抽出し、小さくしたものです。

 CLOSと異なる点は、CLOSがクラスベースのオブジェクトシステムであるのに対し、CORBIT/BOOPSはプロトタイプベースのオブジェクトシステムであるところです。クラスベースのオブジェクトシステムでは各オブジェクトがクラスに属し、多態性が必要な際はクラス間の関係に従ってふるまいを決定します。この「クラス」は、実行時には変更できない言語(JavaとかC++とか)と実行時に変更できる言語(SmalltalkCommon Lisp)がありますが、いずれせよある時点ではなにか「クラス」に属している点では共通しています。一方プロトタイプベースのオブジェクトシステムでは、あるオブジェクトが多態的にふるまう際に「オブジェクトの属するクラス」ではなく、関係する別のオブジェクトに処理を移譲する点が異なります。 (え、これ、あってるのかしら…。書いてみたら理解が雑だった…。)

 現在のCLOSになるまでにいくつかのオブジェクト指向実装があったというのは聞いていましたが、CLOSっぽいものだけでなく、JavaScriptのようなプロトタイプベースのオブジェクト指向システムも存在したんですね。

 ちなみにCommon Lispに詳しくない人のために説明すると、CORBITはCommon LispANSI仕様に含まれているCLOS (Common Lisp Object System)とは別物です。CORBITはCLOSを用いずライブラリとして実現されています。

BOOPS使ってみる

 BOOPSは1ファイルにパッケージが1つ定義されているだけのとても小さな実装です。使うにはお手元のCommon Lisp処理系でファイルをロードするだけで済みます。BOOPSパッケージ内で作業すると若干楽です。

CL-USER> (in-package :boops)
#<Package "BOOPS">

 BOOPSではオブジェクトはシンボルで表現し、アスペクト (aspect; 側面? CLOSでいうスロット、Javaとかだとメンバ/フィールド)をリストで表現します。アスペクトには:value:functionの種別があり、:functionだとオブジェクトが引数に渡されてきます。

 まずはとりあえず、哺乳類をいくつか定義して鳴かせてみます。

;; 哺乳類を定義 (鳴くコマンドつき)
BOOPS> (defobject marmal object
           (toot :function (o) (format t "toot!~%")))
;Compiler warnings :
;   In an anonymous lambda form: Unused lexical variable O
MARMAL

;; わんこを定義 (鳴くコマンド上書き)
BOOPS> (defobject doggo marmal
         (toot :function (o)
               (format t "bow wow!~%")))
;Compiler warnings :
;   In an anonymous lambda form: Unused lexical variable O
DOGGO

;; 鳴く
BOOPS> (message 'marmal 'toot)
toot!
NIL
BOOPS> (message 'doggo 'toot)
bow wow!
NIL

 継承できた!

 次に、アドホックにメソッドを追加してみます。これには匿名オブジェクト(というかgensym)を作ります。このように。

;; 匿名オブジェクトとしてジョンを定義しアスペクトを追加を追加
BOOPS> (let ((john (a doggo (introduce :function (o)
                                       (format t "I'm John! ")
                                       (message o 'toot)))))
         (message john 'introduce))
I'm John! bow wow!
NIL

 静的なクラスに依存しないので、このようなこともできます(ただしCLOSは静的ではないので同じようなことできそう)。

BOOPS> (defobject human marmal
         (toot :function (o)
               (format t "hello!~%")))
;Compiler warnings :
;   In an anonymous lambda form: Unused lexical variable O
HUMAN
BOOPS> (let ((tom (a human (introduce :function (o)
                                      (format t "I'm Tom! ")
                                      (message o 'toot)))))
         (message tom 'introduce))
I'm Tom! hello!
NIL

 これでアドホックなインターフェースっぽいサムシングが実現できました。

中身について

 だいぶシンプルです。BOOPSではオブジェクトはシンボルだと言いました。BOOPSの内部的には、オブジェクトのアスペクトは、シンボルのpslitスロットにplistの形で入っています。

;; BOOPSで定義されているオブジェクトのアスペクト
BOOPS> (symbol-plist 'object)
(ASPECTS ((SET-VALUE :FUNCTION . #<Anonymous Function #x302000C8FF7F>) (SHOW :FUNCTION . #<Anonymous Function #x302000C900DF>)) ISA NIL)

;; わんこのアスペクト
BOOPS> (symbol-plist 'doggo)
(ASPECTS ((TOOT :FUNCTION . #<Anonymous Function #x302000C9835F>)) ISA MARMAL)
BOOPS>

 匿名オブジェクトもgensymであるただのシンボルなのでplistスロットがあり、おなじようにアスペクトが入っています。

BOOPS> (let ((tom (a human (introduce :function (o)
                                      (format t "I'm Tom! ")
                                      (message o 'toot)))))
         (symbol-plist tom))
(ASPECTS ((INTRODUCE :FUNCTION . #<Anonymous Function #x302000E3B3AF>)) ISA HUMAN)

 おしまい。

*1:Common Lisp Musicについて調べていたら"Compact LISP Machine"なる論文を見つけ、論文に出てくるTexas InstrumentsExplorerというLISPマシンを調べていたらCMUアーカイブに流れ着き、そこを眺めていたらあった。

*2:論文情報はこちら(読めぬ): https://ai.vub.ac.be/publications/236 。著者の方はGuy. L. SteeleさんではなくファーストネームがSteelsさん。