CLOS

CLOSとはCommon Lisp Object Systemの略らしい.OOというのはメソッドというしばしば破壊的な関数がバンバンでてきて,Lispとは相容れない世界かと思っていたのだが,そうではないようだ.
クラス定義とインスタンンス化とスロットの参照
メソッド
スロット
優先度
総称関数
スロットの継承
補助メソッド
オペレータメソッドコンビネーション
総称関数の削除

クラス定義とインスタンンス化とスロットの参照

クラスはdefclassで定義する.
(defclass クラス名 スーパークラスのリスト スロットのリスト)
スロットとは,多くのOOプログラミング言語でスロットと呼ばれているものに該当する.以下は二つのスロットfoo,barをもつhogeというクラスを定義している.
> (defclass hoge ()
      (foo bar))
#<STANDARD-CLASS HOGE>
そして以下はスロットpiyoを持つhogeのサブクラスfugaを定義している.
> (defclass fuga (hoge)
      (piyo))
#<STANDARD-CLASS FUGA>
インスタンス化はmake-instanceで行う.
(make-instance クラスのシンボル)
make-instanceは指定したクラスのインスタンスを返す.以下はこれをsetfで変数に括りつける.
> (setf hogeinstance (make-instance 'hoge))
#<HOGE #x20400B09>
> hogeinstance 
#<HOGE #x20400B09>
> (setf fugainstance (make-instance 'fuga))
#<FUGA #x20404A79>
> fugainstance 
#<FUGA #x20404A79>
hogeinstance,fugainstanceにそれぞれクラスhoge,fugaのインスタンスが括りつけられた.インスタンスのスロットはslot-valueで参照できる.
(slot-value インスタンス スロットのシンボル)
以下は上でインスタンス化したfugaのインスタンスのスロットfoo,piyoを設定,参照している.
> (setf (slot-value fugainstance 'foo) 1)
1
> (setf (slot-value fugainstance 'piyo) 2)
2
> (slot-value fugainstance 'foo)
1
> (slot-value fugainstance 'piyo)
2
上のクラス定義では,インスタンス化しただけではスロットが初期化されないので,インスタンス化後いきなりスロットを参照するとエラーとなる.
> (slot-value hogeinstance 'foo)

*** - Condition of type UNBOUND-SLOT.
The following restarts are available:
STORE-VALUE    :R1      You may input a new value for (SLOT-VALUE # FOO).
USE-VALUE      :R2      You may input a value to be used instead of (SLOT-VALUE # FOO).

> (setf (slot-value hogeinstance 'foo) 100)
100
> (slot-value hogeinstance 'foo)
100

メソッド

CLOSではメソッドはクラスとは別途定義する.
(defmethod メソッド名 ((インスタンス変数 クラス)*)
  本体)
以下は,二つのスロットfirstname,lastnameを持つクラスpersonのメソッドを定義している.
> (defclass person ()
      (firstname lastname))
#<STANDARD-CLASS PERSON>
> (defmethod whatisyourname ((you person))
      (format t "~A ~A~%"
       (slot-value you 'firstname)
       (slot-value you 'lastname)))
#<STANDARD-METHOD (#)>
> (setf matsu (make-instance 'person))
#<PERSON #x203F0A55>
> (setf (slot-value matsu 'firstname) 'ma)
MA
> (setf (slot-value matsu 'lastname) 'tsu)
TSU
> (whatisyourname matsu)
MA TSU
NIL

スロット

クラスを定義する際に,スロットにいろいろな属性を付加することができる.
:accessor
slot-valueとは別にスロットを参照できる関数を暗黙に定義する.
:reader
:accessorは読み書きのためのアクセサ関数を定義するが,:readerは読み込み専用関数を暗黙に定義する.定義された関数の引数はインスタンスである.
:write
:accessorは読み書きのためのアクセサ関数を定義するが,:readerは書き込み専用関数を暗黙に定義する.定義された関数の第一引数は設定値,第二引数がインスタンスである.
:initform
該当スロットのデフォルト値を定義.
:initarg
make-instance時にスロットに初期値設定を行えるようにする.:initargの後には:で始まる識別子を指定して,make-instance時にはその識別子を介して値を設定する.

(defclass clazz ()
((slot :initarg :slotIni)))

(slot-value (make-instance ‘clazz :slotIni 5) ‘slot)
5
:allocation
引数に:classを指定すると,該当スロットはインスタンス間で共有される.つまり該当フスロットがいわゆるクラスフィールドとなる.:instanceはデフォルトで,つまりインスタンスフィールドである.
:documentation
スロットのドキュメント.
:type
スロットの型を指定.
以下にこれらのいくつかを使用する例を示す.
(defclass hogefuga ()
((slotA :reader getSlotA
:writer setSlotA
:initform 0
:initarg :slotAIni)
(classShared :allocation :class
:initform 0
:accessor accessToClassShared)))

;; :initargで指定したslotAIniを使用して,slotAを999で初期化する

(setf hogefugaInstance (make-instance ‘hogefuga :slotAIni 999))

(setf hogefugaInstance2 (make-instance ‘hogefuga))

;; :readerと:writerの確認.

(getslota hogefugainstance )
999
(setslota 100 hogefugainstance )
100
(getslota hogefugainstance )
100

;; :allocation :classの確認.
;; hogefugainstanceとhogefugainstance2の
;; classSharedの実体は同じである.

(accesstoclassshared hogefugainstance )
0
(setf (accesstoclassshared hogefugainstance2 ) 99)
99
(accesstoclassshared hogefugainstance )
99

優先度

defclassの引数から明らかなように,CLOSでは多重継承できる.多重継承下での振舞いを理解するには,優先度を理解する必要がある.以下のような継承関係を考える.

これらのクラスを実装してみる

(defclass superS () (x))
(defmethod method ((x superS))
(format t “method of superS~%”))

(defclass superA (superS) (x))
(defmethod method ((x superA))
(format t “method of superA~%”))

(defclass superB (superS) (x))
(defmethod method ((x superB))
(format t “method of superB~%”))

(defclass classAB (superA superB) (x))
(defclass classBA (superB superA) (x))

(format t “~%=====================~%”)

(setf instanceAB (make-instance ‘classAB))
(method instanceAB)

(setf instanceBA (make-instance ‘classBA))
(method instanceBA)

(format t “=====================~%”)

;(defclass classSAB (superS superA superB) (x))

サンプルを実行してみる.
$> clisp inherit.lisp 
=====================
method of superA
method of superB
=====================
多重継承の際には,継承クラスの指定順には意味がある.サンプルでは,二つの親クラスsuperA,superBを二つのパターンで継承した.
(defclass classAB (superA superB) (x))
(defclass classBA (superB superA) (x))
classABのメソッドmethodを呼び出すと,superAのmethodが呼び出され,classBAではsuperBのmethodが呼び出されることがわかる.CLOSでは,クラスを継承する際に,優先度というものが特定のアルゴリズムで決定される.あるクラスの祖先への系譜を優先度の順に並べた優先度リストは以下のように作成できる.
tを上,優先度リストを調べるクラスを最下にとる.
深さ優先,左優先探索で祖先へ向かって親クラスを並べる.
親への経路が右にもある場合,その親はたどらず,別の親を辿る.
tまで辿りついたら終了.
すべてのクラスはtのサブクラスstandard-objectを親に持つ.以下のようなクラス関係があったとする.
こんなのを実装したコードなど見たくもないが,とにかくこの状況ではクラスGの優先度リストは以下のように作成できる([]内が優先度リスト).
Gの一番左の親はC([C]).
Cの親はAだが,Aへ至る経路が右にもあるので,探索地点をCからGに戻す([C]).
Gの次の親はD([CD]).
Dの親はAだが,Aへ至る経路が右にもあるので,探索地点をDからGに戻す([CD]).
Gの次の親はE([CDE]).
Eの親はAで,Aへ至る経路が右にはもうない([CDEA]).
Aの親はstandard-objectだが,standard-objectへ至る経路が右にもあるので,探索地点をAからEに戻す([CDEA]).
Eにはもう親がないので,探索地点をEからGに戻す([CDEA]).
Gの次の親はF([CDEAF]).
Fの親はBで,Bへ至る経路が右にはもうない([CDEAFB]).
Bの親はstandard-objectで,standard-objectへ至る経路が右にはもうない([CDEAFB,standard-object]).
standard-objectの親はt([CDEAFB,standard-object,t]).
こうしてGの優先度リスト[CDEAFB,standard-object,t]が得られる.これらのクラスに重複する名前のメソッドがある場合,優先度の最も高いメソッドが呼ばれる.先のサンプルで,classABの優先度リストは[superA,superB,superS]である.ここでサンプルを以下のようにクラスsuperAのメソッドmethodの定義をコメントアウトする.
(defclass superA (superS) (x))
;(defmethod method ((x superA))
;  (format t "method of superA~%"))
この状態で実行すると,
(setf  instanceAB (make-instance 'classAB))
(method instanceAB)
superSではなく,superBのメソッドが呼ばれる.classABではsuperSよりsuperBの方が優先度が高いからである.
$> clisp inherit.lisp 
=====================
method of superB
method of superB
=====================
先のサンプル末尾のコメントアウトされた式
;(defclass classSAB (superS superA superB) (x))
は,親と親の親を直接継承するクラスSABを定義しようとするものである.優先度決定アルゴリズムからして,全く意味がないことは明らかだが,そのせいか,この式を実行しようとするとエラーとなる.
$> clisp inherit.lisp 
=====================
method of superA
method of superB
=====================
*** - DEFCLASS CLASSSAB: inconsistent precedence graph, cycle
  (#<STANDARD-CLASS SUPERA> #
	

総称関数

総称関数というのは,メソッドの集まりである.具体的には以下のようなことができる.
(defmethod genfun ((param hoge))
(format t “param class : hoge~%”))
(defmethod genfun ((param fuga))
(format t “param class : fuga~%”))
メソッドの名前と引数の数が同じである.で,関数が呼ばれたときは,引数の型によってどのメソッドを実行するかが決定される.すなわち,メソッド定義での引数の優先度が,実際に指定された引数の優先度以下で最高のもの(※)を実行する.以下の例を考える.

(defclass hoge () (x))
(defclass fuga (hoge) (x))
(defclass foo (fuga) (x))
(defclass bar (foo) (x))

(defmethod genfun ((param hoge))
  (format t "param class : hoge~%"))
(defmethod genfun ((param fuga))
  (format t "param class : fuga~%"))
(defmethod genfun ((param bar))
  (format t "param class : bar~%"))

(defmethod genfun2 ((param1 hoge) (param2 fuga))
  (format t "param class : hoge fuga~%"))
(defmethod genfun2 ((param1 fuga) (param2 hoge))
  (format t "param class : fuga hoge~%"))

(format t "~%==================================~%")

(setf inshoge (make-instance 'hoge))
(genfun inshoge)

(setf insfuga (make-instance 'fuga))
(genfun insfuga)

(setf insfoo (make-instance 'foo))
(genfun insfoo)

(genfun2 insfuga insfuga)

(format t "==================================~%")

これを実行すると,以下のようになる.
$> clisp  generic_function.lisp
==================================
param class : hoge
param class : fuga
param class : fuga
param class : fuga hoge
==================================
優先度リストは(bar,foo,fuga,hoge,standard-object,t)である.特に出力から
(setf insfoo (make-instance 'foo))
(genfun insfoo)
では,
(defmethod genfun ((param fuga))
  (format t "param class : fuga~%"))
が呼ばれている.呼んだ時の引数はクラスfooなので,fooよりも特定的な引数をとる
(defmethod genfun ((param bar))
  (format t "param class : bar~%"))
は除外さる.そして残った
(defmethod genfun ((param hoge))
  (format t "param class : hoge~%"))
(defmethod genfun ((param fuga))
  (format t "param class : fuga~%"))
のうち,hogeよりfugaの方が特定的なので,fuga側が呼ばれた.また,
(genfun2 insfuga insfuga)
によって,
(defmethod genfun2 ((param1 hoge) (param2 fuga))
  (format t "param class : hoge fuga~%"))
(defmethod genfun2 ((param1 fuga) (param2 hoge))
  (format t "param class : fuga hoge~%"))
のうち後者が呼ばれた.引数が複数ある場合を踏まえて実行メソッド決定のルールを整理してみた.
全引数が特定化の範囲内である(対応する引数全てにおいて,呼び出し時の引数の優先度がメソッド定義の引数の優先度以上).このようなメソッドを適用可能なメソッドという.適用可能なメソッドがなければエラーとなる.
適用可能なメソッドの引数の優先度を左から順に比べる.最初により特定的なメソッドが見付かれば,それが採用される.
後者のルールにより,サンプルでは
(defmethod genfun2 ((param1 fuga) (param2 hoge))
  (format t "param class : fuga hoge~%"))
が呼ばれたのである.最初の引数がhogeとfugaではfugaの方が特定的であるのでその時点で呼び出しメソッドが決定しているので,第二引数は関係ない(もちろん適用可能条件は満たす必要がある).もっとも特定的でないメソッドを定義することで,メソッドの定義洩れを防ぐことができる.
(defmethod genfun ((param))
  (format t "param class : not specified~%"))
こうすることで,あらゆるgenfun呼び出しの適用可能メソッドには少なくとも上のメソッドが含まれる.
※ このようなとき,「特定的」と言葉を使用することがある.「優先度が(より)高い」 = 「より特定的」.

スロットの継承

クラスとともにスロットも継承される.このとき,スロットの属性の継承ルールは以下のようになっている.
:allocation,:initform,:documentation
最も特定的なクラスのものを適用.
:initarg,:accessor,:reader,:writer
和.継承のたびに追加的に設定されていく.
:type
積.継承のたびに「かつ」な型がとられていく.
以下のサンプルで,:initform,:reader,:writerの継承の様子を確認する.

(defclass hoge ()
  ((slotA :initform 'hoge
	  :reader getSlotA)))

(defclass fuga (hoge)
  ((slotA :initform 'fuga
	  :writer setSlotA)))

;; initform���ǧ
(format t "initform of hoge : ~A~%"
	(getSlotA (make-instance 'hoge)))

;; initform���ǧ
(setf fugainstance (make-instance 'fuga))
(format t "initform of fuga : ~A~%"
	(getSlotA fugainstance))

;; �᥽�å�setSlotA������.
(setSlotA 'fugafuga fugainstance)
(format t "slotA of fuga : ~A~%"
	(getSlotA fugainstance))
実行結果.
$> clisp inherit_slot.lisp
initform of hoge : HOGE
initform of fuga : FUGA
slotA of fuga : FUGAFUGA
スロットslotAに関して,:initformは最も特定的なものが採用され,:reader,:writerはクラスfugaでは両方使用できている.

補助メソッド

今までのメソッドを基本メソッドと呼び,これに対して補助メソッドというものがある.
(defmethod メソッド名 :around ....)
(defmethod メソッド名 :before ....)
(defmethod メソッド名 :after ....)
このように補助メソッドは基本メソッド定義のメソッド名の後に:before,:after,:aroundを記述することで定義する.
:around
基本メソッドの代わりに,:aroundがついたメソッド(最も特定的なもの)を呼び出す.
:before
:aroundなメソッドがなければ,基本メソッド呼び出しの前に,適用可能かつ:beforeなものを特定性の高いものから順に全て呼び出す.
:after
:aroundなメソッドがなければ,基本メソッド呼び出しの後に,適用可能かつ:afterなものを特定性の低いものから順に全て呼び出す.
基本メソッド呼び出しは前述の通り,最も特定的なものだけ呼び出される.:before,:afterを使用する例を以下に示す.
(defclass hoge ()
  ((slotA)))

(defclass fuga (hoge)
  ((slotB)))

(defmethod methodA ((param hoge))
  (format t "methodA hoge~%")
  'ret-methodA-hoge)

(defmethod methodA :before ((param hoge))
  (format t "before methodA hoge~%")
  'ret-before-methodA-hoge)

(defmethod methodA :after ((param hoge))
  (format t "after methodA hoge~%")
  'ret-after-methodA-hoge)

(defmethod methodA ((param fuga))
  (format t "methodA fuga~%")
  'ret-methodA-fuga)

(defmethod methodA :before ((param fuga))
  (format t "before methodA fuga~%")
  'ret-before-methodA-fuga)

(defmethod methodA :after ((param fuga))
  (format t "after methodA fuga~%")
  'ret-after-methodA-fuga)

(format t "~%")
(format t "=========================================~%")
(format t "ret = ~A~%" (methodA (make-instance 'hoge)))
(format t "=========================================~%")
(format t "ret = ~A~%" (methodA (make-instance 'fuga)))
(format t "=========================================~%")

以下実行結果.

$> clisp before_after_standard_method_combination.lisp
=========================================
before methodA hoge
methodA hoge
after methodA hoge
ret = RET-METHODA-HOGE
=========================================
before methodA fuga
before methodA hoge
methodA fuga
after methodA hoge
after methodA fuga
ret = RET-METHODA-FUGA
=========================================
確かに,methodA:beforeは特定性の高いものから順に,methodA:afterは特定性の低いものから順に適用可能なものは全て呼び出されている.さらにmethodA:before,methoA:afterの呼び出しにもかかわらず基本メソッドの値が返り値として使用されている点に注意.このように,methodA:beforeとmethodA:afterは,基本メソッドを補助するように基本メソッド呼び出しの前後で呼び出される.上のサンプルにさらに:around補助メソッドを追加してみる.
(defclass hoge ()
  ((slotA)))

(defclass fuga (hoge)
  ((slotB)))

(defmethod methodA ((param hoge))
  (format t "methodA hoge~%")
  'ret-methodA-hoge)

(defmethod methodA :before ((param hoge))
  (format t "before methodA hoge~%")
  'ret-before-methodA-hoge)

(defmethod methodA :after ((param hoge))
  (format t "after methodA hoge~%")
  'ret-after-methodA-hoge)

(defmethod methodA :around ((param hoge))
  (format t "around methodA hoge~%")
;  (if (next-method-p) (call-next-method))
  'ret-around-methodA-hoge)

(defmethod methodA ((param fuga))
  (format t "methodA fuga~%")
  'ret-methodA-fuga)

(defmethod methodA :before ((param fuga))
  (format t "before methodA fuga~%")
  'ret-before-methodA-fuga)

(defmethod methodA :after ((param fuga))
  (format t "after methodA fuga~%")
  'ret-after-methodA-fuga)

(defmethod methodA :around ((param fuga))
  (format t "around methodA fuga~%")
;  (if (next-method-p) (call-next-method))
  'ret-around-methodA-fuga)

(format t "~%")
(format t "=========================================~%")
(format t "ret = ~A~%" (methodA (make-instance 'hoge)))
(format t "=========================================~%")
(format t "ret = ~A~%" (methodA (make-instance 'fuga)))
(format t "=========================================~%")
以下実行結果.
$> clisp around_standard_method_combination.lisp 
=========================================
around methodA hoge
ret = RET-AROUND-METHODA-HOGE
=========================================
around methodA fuga
ret = RET-AROUND-METHODA-FUGA
=========================================
このように,methodA:aroundがある場合は,:before,:afterのみならず基本メソッドも呼び出されない.さらにmethod:aroundは最も特定的なもののみが呼び出される.サンプルのfugaのインスタンスを引数としたmethodA:around内のif式のコメントアウトを外すと以下のようになる.
$> clisp around_standard_method_combination.lisp 
=========================================
around methodA hoge
ret = RET-AROUND-METHODA-HOGE
=========================================
around methodA fuga
around methodA hoge
ret = RET-AROUND-METHODA-FUGA
=========================================
call-next-methodは「次のメソッド」を呼び出す.next-method-pは「次のメソッド」があれば真を返す.「次」というのは,次のリストの条件を順に調べて最初にマッチするものである(※).
より特定性の低い:aroundなメソッド
:beforeなメソッド
基本メソッド
:afterなメソッド
上のサンプル実行結果では,fugaのインスタンスを引数とするmethodA:aroundから,hogeのインスタンスを引数とするmethodA:aroundが最初にマッチするので,これが呼び出されている.次に,hogeのインスタンスを引数とするmethodA:around内のif式のコメントアウトを外す以下のようになる.
$> clisp around_standard_method_combination.lisp 
=========================================
around methodA hoge
before methodA hoge
methodA hoge
after methodA hoge
ret = RET-AROUND-METHODA-HOGE
=========================================
around methodA fuga
around methodA hoge
before methodA fuga
before methodA hoge
methodA fuga
after methodA hoge
after methodA fuga
ret = RET-AROUND-METHODA-FUGA
=========================================
出力の後半,fugaインスタンスを引数にとるmethodA:aroundは,hogeインスタンスを引数にとるmethodA:aroundを呼び出し,そこからさらにmethodA:before呼び出しへと続いている.なお,:aroundなメソッドが呼び出された場合は,たとえそこから基本メソッドが呼び出されたとしても:aroundなメソッド自身の返り値が返っている点に注意.
※ このルールで呼び出されるメソッドの組合せを標準メソッドコンビネーションという.

オペレータメソッドコンビネーション

標準メソッドコンビネーションに対して,オペレータメソッドコンビネーションというコンビネーションがある.標準メソッドコンビネーションでは,基本メソッドは最も特定的なもののみが呼び出され,さらに補助メソッドが呼び出された.オペレータメソッドコンビネーションでは,以下のルールでメソッドが呼び出される.
:around
基本メソッドの代わりに,:aroundがついたメソッド(最も特定的なもの)を呼び出す.
基本メソッド
適用可能な:aroundなメソッドがない場合,基本メソッドを特定的なものから非特定なものに順に(適用可能な範囲内で)呼び出される.各基本メソッドの呼び出し結果はdefgenericにより設定した関数で結合される.
オペレータメソッドコンビネーションのサンプルを以下に示す.

(defgeneric methodA (x)
  (:method-combination list))

(defclass hoge ()
  ((slotA)))

(defmethod methodA list ((param hoge))
  "methodA hoge")

(defclass fuga (hoge)
  ((slotA)))

(defmethod methodA list ((param fuga))
  "methodA fuga")

まず
(defgeneric methodA (x)
  (:method-combination list))
とすることで,:aroundがない場合の複数の基本メソッド呼び出し結果の結合方法をlistに指定している.
(defgeneric メソッド名 (引数)
  (:method-combination オペレータ))
おそらく引数で特定度を指定できる.オペレータというよりコンビネーションタイプと呼んだ方が正しいかもしれない.オペレータには以下が指定できる.
+
and
append
list
max
min
nconc
or
progn
standard
オペレータがstandard以外だと,該当基本メソッドの補助メソッド(:before,:afterなメソッド)を定義できなくなる.これ以後,methodA呼び出しの引数をdefgenericで指定したオペレータとすることに注意する.
(defmethod methodA list ((param hoge))
  "methodA hoge")
といった具合である.実行してみる.
$> clisp -i operator_method_combination.lisp
>  (methodA (make-instance 'hoge))
("methodA hoge")
> (methodA (make-instance 'fuga))
("methodA fuga" "methodA hoge")
二回目のmethodA呼び出しの引数はfugaのインスタンスだが,返り値は
("methodA fuga" "methodA hoge")
となっている.これは基本メソッドを特定的なものから非特定なものに順に(適用可能な範囲内で)呼び出し,それを関数listで結合したものである.この後
> (defmethod methodA :around ((param hoge))
 "methodA:around hoge")

WARNING:
The generic function
  #<GENERIC-FUNCTION METHODA> is being modified,
  but has already been called.
#<STANDARD-METHOD :AROUND (#<STANDARD-CLASS HOGE>)>
とすると,警告がでる.methodAは既に呼び出されているが,このdefmethodによって今後は動作がかわることを警告している(たぶん普通はこんなことしない).実際
>  (methodA (make-instance 'hoge))
"methodA:around hoge"
> (methodA (make-instance 'fuga))
"methodA:around hoge"
と,先程と動作が異なっている.

総称関数の削除

メソッドコンビネーションを一度指定してしまうと,変更できない.
> (defclass hoge () ())
#<STANDARD-CLASS HOGE>
> (defclass fuga (hoge) ())
#<STANDARD-CLASS FUGA>
> (defgeneric methodA (x)
(:method-combination or))
#<GENERIC-FUNCTION METHODA>
> (defmethod methodA or ((instance hoge)) t)
#<STANDARD-METHOD OR (#<STANDARD-CLASS HOGE>)>
> (defmethod methodA or ((instance fuga)) nil)
#<STANDARD-METHOD OR (#<STANDARD-CLASS FUGA>)>
> (methodA (make-instance 'hoge))
T
> (methodA (make-instance 'fuga))
T
> (defmethod methodA + ((instance hoge)) 10)

*** - OR method combination, used by #<GENERIC-FUNCTION METHODA>,
allows no method qualifiers except (OR :AROUND):
#<STANDARD-METHOD + (#<STANDARD-CLASS HOGE>)>
> (defgeneric methodA (x)
(:method-combination +))

WARNING:
The generic function #<GENERIC-FUNCTION METHODA> is being modified, but has already been called.
*** - + method combination, used by #<GENERIC-FUNCTION METHODA>,
allows no method qualifiers except (+ :AROUND):
#<STANDARD-METHOD OR (#<STANDARD-CLASS FUGA>)>
変更したい場合は,一度総称関数そのものを削除する必要がある.総称関数の削除にはfmakunboundを使用する.
> (fmakunbound 'methodA)
METHODA
> (defgeneric methodA (x)
(:method-combination +))
#<GENERIC-FUNCTION METHODA>
> (defmethod methodA + ((instance hoge)) 10)
#<STANDARD-METHOD + (#<STANDARD-CLASS HOGE>)>
> (defmethod methodA + ((instance fuga)) 1)
#<STANDARD-METHOD + (#<STANDARD-CLASS FUGA>)>
> (methodA (make-instance 'hoge))
10
> (methodA (make-instance 'fuga))
11

This article was written by Fujiko