2010年9月24日金曜日

Common Lispで (object method args...)の形式でメソッド呼び出し

Common Lispのオブジェクトシステムはそのまんまのネーミングで、 Common Lisp Object System (CLOS:シーロス、クロス)と言います。

CLOSでは、メソッド呼び出しが普通の関数呼び出しと同じように (method object args...) という形式になっていますが、これは他のオブジェクト指向言語から見たらへんてこな順序で、わかりにくいかもしれません。 Javaを触ってきた人からすると、(object method args...)と書きたいでしょう。たぶん。

当然、用意されている書き方が気にくわないならば自分で書き換えてしまうのがCommon Lisp なので、解決方法はいくつかあるかと思います。

仕様にはなっていませんが、CLOSに加えて Meta Object Protocol(MOP)という、デファクトスタンダード的なメタプログラミングの方法が用意されています。

マクロを使うのも良いかもしれませんが、たまには違う方法で実現してみましょう、ということで MOPを利用してみました。

MOPには処理系毎の差異を吸収するラッパーライブラリも存在しますが、面倒くさいので処理系依存のコードを書いてしまいます。

(defclass funcallable-base-class ()
()
(:metaclass sb-mop:funcallable-standard-class))

(defmethod initialize-instance :after ((obj funcallable-base-class) &rest args)
(declare (ignore args))
(sb-mop:set-funcallable-instance-function
obj
#'(lambda (method &rest args)
(apply method obj args))))

(defun callable-object-reader (stream ch1 ch2)
(declare (ignore ch1 ch2))
`(lambda (&rest args)
(apply ,(read stream) args)))

(set-dispatch-macro-character
#\# #\^
'callable-object-reader)

(defmacro defclass! (name direct-superclasses direct-slots &rest options)
`(defclass ,name (funcallable-base-class ,@direct-superclasses)
,direct-slots
(:metaclass sb-mop:funcallable-standard-class)
,@options))


;; example

(defclass! hoge ()
((a :accessor a-of :initform 0 :initarg :a)))
(defclass! fuga ()
((b :accessor b-of :initform 0 :initarg :b)))

(defmethod dump ((obj hoge))
(format t "hoge class: ~A~%" (a-of obj)))
(defmethod dump ((obj fuga))
(format t "fuga class: ~A~%" (b-of obj)))


(defparameter obj (make-instance 'hoge :a 20))

;; 1. funcallで呼び出す
(funcall obj 'dump)
;; : hoge class: 20
;; => nil

;; 2. 関数スロットにセットして関数呼び出し
(setf (symbol-function 'obj) obj)
(obj 'dump)
;; : hoge class: 20
;; => nil

;; 3. リーダマクロを利用してlambdaで包む
(#^obj 'dump)
;; : hoge class: 20
;; => NIL

(#^(make-instance 'fuga :b 10) 'dump)
;; : fuga class: 10
;; => nil

メタクラスにfuncallable-standard-classを指定したクラスのオブジェクトは、 set-funcallable-instance-functionでセットした関数を、普通の関数と同じように呼び出すことができます。

これで、シンボルの関数スロットにオブジェクトをセットすれば、(object method args...)という形式でメソッド呼び出しが出きるようになります。

0 件のコメント:

コメントを投稿