#:g1: MIT Lisp Machine: Advising a Functionの紹介

Posted 2014-09-11 23:00:00 GMT

(LISP Library 365参加エントリ)

 LISP Library 365 の255日目です。

MIT Lisp Machine: Advising a Functionとはなにか

 MIT Lisp Machine: Advising a Functionは、MIT系Lispマシンが提供していたアドバイス機構です。
ZetalispとCommon Lispでコードはほぼ同一ですが、MIT CADR、LMI LambdaはZetalispで、TI Explorerは、Common Lispで書かれているようです。なお、Symbolicsにも存在しますが、Common LispかZetalispかは不明です。
また、Allegro CLが提供する旧アドバイス機構もほぼMIT LispMのインターフェイスをそのまま実現していたようです。

パッケージ情報

パッケージ名MIT Lisp Machine: Advising a Function
LispマシンマニュアルLisp Machine Manual: 31.10 Advising a Function
Allegro CLマニュアルAllegro CL: Advice

インストール方法

 Lispマシンでは、sysパッケージで定義されていて、userパッケージから使えるようになっています。
Allegro CLではexclで定義されています。

試してみる

 前回の紹介で、大抵の処理系では、adviceで、Clozure CLでは、adviseなのがややこしいと書きましたが、どうも伝統的には、adviseだったようです。
定義構文でdefadviceのような物を多く目にしていたので、こっちが標準的かと思っていました。しかし、adviseも大抵マクロで実装されているので、命令する関数というよりは、定義フォームという感じなのですが…。

 さて、MIT Lispマシンでは、アドバイス機構は、Encapsulationsというより汎用的な仕組みの上に構築されていて、この機能を利用するものには、Advice以外にも、Traceや、Breakon等々があるようです。

 書法と動作ですが、

(defun matu (x)
  (format t ">> ~A" x)
  (terpri))

(matu 42.) ;>> >> 52

こんな感じの関数があったとすれば、

(advise matu :before :b0 0
  (format t "..before0:~%"))

(advise matu :before :b1 1 (format t "..before1:~%"))

(advise matu :after :a0 0 (format t "..after0:~%"))

(advise matu :after :a1 1 (format t "..after1:~%"))

(advise matu :around :ar0 0 (format t "==>around0:~%") :do-it (format t ">==around0:~%"))

(advise matu :around :ar1 1 (format t "==>around1:~%") :do-it (format t ">==around1:~%"))

(matu 42.) ;>> ..before0: ;>> ..before1: ;>> ==>around0: ;>> ==>around1: ;>> >> 52 ;>> >==around1: ;>> >==around0: ;>> ..after0: ;>> ..after1: ;=> NIL

こんな感じに書けます。
引数は、左から

  1. 適用する関数名
  2. :before、:after、:aroundのクラスを指定
  3. アドバイスの名前
  4. 適用する順番を指定(数値の他シンボルも可)
  5. ボディ

というところで、:aroundの場合、ボディ内で:do-itを記述することで元の関数を呼び出します。元の引数リストはarglistで参照可能。
Allegro CLの場合は、excl:defadviceというadviseをもうちょっと定義構文っぽくしたものも提供されています。

within

 Allegro CLには存在せずオリジナルのLispマシン独自の機能がadvise-withinです。

(advise (:within foo matu) :before :w//foo//b nil
  (format t "in foo: args:(~S)~%" arglist))

(advise-within foo matu :before :w//foo//b nil (format t "in foo: args:(~S)~%" arglist))

のようにadvise-withinでもadviseでも書けるのですが、上記の場合、fooがmatuを呼び出した時だけ効くアドバイスになります。
実行するとこんな感じ。

(bar 42.)
..before0:
..before1:
==>around0:
==>around1:
>> 52
>==around1:
>==around0:
..after0:
..after1:

(foo 42.) in foo:(52) ..before0: ..before1: ==>around0: ==>around1: >> 52 >==around1: >==around0: ..after0: ..after1:

 fooの内側ならどんなに階層が深くてもいけたりするのか試してみましたが、直の呼び出しでないと駄目みたいです。
withinを利用すれば、特定の関数から呼ばれた場合のみbreakするとか色々応用はできそうですが、実際のところ便利なのかどうか。
なお、マクロでも試してみましたが、定義してもエラーにはならないものの、誰から呼び出されることになるのかいまいち不明のため結果がどうなるか不明でした。

まとめ

 今回は、MIT Lisp Machine: Advising a Functionを紹介してみました。
MIT LispマシンのEncapsulations機構と、Advice機構のコードは、眺めて感じではRMSが書いてるような気がするのですが、実際のところどうなのか確かめてみたいところです。
なんとなくRMSの書法っぽいのと、妙なアイデア(:withinとか)が盛り込まれているところがRMSっぽいです。

comments powered by Disqus