#:g1: Allegro CL: Fwrapの紹介

Posted 2014-04-20 15:00:00 GMT

(LISP Library 365参加エントリ)

 LISP Library 365 の111日目です。

Allegro CL: Fwrapとはなにか

 Allegro CL: Fwrapは、Allegro CLが提供する進化版のADVICE機構です。

パッケージ情報

パッケージ名Allegro CL: Fwrap
ドキュメントサイトFwrapping and Advice

インストール方法

 標準の状態でAllegro CLのEXCLパッケージに含まれています。

試してみる

 ADVICE機構は、恐らく最近ではAOP的な機能として考えられることが多いのではないでしょうか。
Lisp処理系には60年代後半のBBN-LISPあたりからADVICE機構が含まれていましたが、Common Lispには取り入れられなかっためか、それ程馴染みがない機能となってしまった感があります。
現在メジャーなLisp方言でサポートしているものといえば、Emacs Lispがありますが、構文的には、どうもLispマシンのADVICEと似たもののようです。
Common Lispの処理系にも、大抵Lispマシンと同様のADVICE的な機能が用意されていますが、CLOSのメソッド・コンビネーションで似たようなこともできるので、そちらを利用することが多いのかもしれません〈とはいえ微妙に使いたい場面が違いますが〉

 Allegro CL: Fwrapは、そんなADVICE機構の従来の問題点を改善したものとのこと。
使い方は単純で、

(defun bamatu (x)
  (format t "~A~%" x))

(defvar *bamatu-old* #'bamatu)

(funcall *bamatu-old* 42) ;>> 42 ;>> ;=> NIL

のような関数を定義していたとすると、

(def-fwrapper bamatu-wrapper (x)
  (format t "before~%")
  (call-next-fwrapper)
  (format t "after~%"))

のようにラッパーを定義できます。
メソッド・コンビネーションの:aroundに似た感じで、call-next-fwrapperを呼ばないと元の関数は呼び出されません。

(fwrap 'bamatu 'w1 'bamatu-wrapper)
;=>  #<Function BAMATU>

(describe #'bamatu) ;>> #<Function BAMATU> is a TENURED COMPILED-FUNCTION. ;>> The arguments are (X) ;>> It has the following indicator/fwrapper pairs, from outer to inner: ;>> W1 #<Function BAMATU-WRAPPER> ;>> ;=> <no values>

(eq *bamatu-old* #'bamatu) ;=> T (bamatu 42) ;>> before ;>> 42 ;>> after ;>> ;=> NIL

(funcall *bamatu-old* 42) ;>> before ;>> 42 ;>> after ;>> ;=> NIL

(funwrap 'bamatu 'w1) ;=> #<Function BAMATU>

(bamatu 42) ;>> 42 ;>> ;=> NIL

(funcall *bamatu-old* 42) ;>> 42 ;>> ;=> NIL

定義したラッパーは、fwrapで活性化させます。明示的に活性化しないと特に何も起きません。
Emacs Lispのdefadviceも定義と活性化の二段階に分けることができるので似ているかもしれません。
ラッパーを付けた前後で関数がeqなことが改善点の一つとのこと。

マクロにラップ

 また、マクロにもラップ可能です。

(defmacro begin (&body body)
  `(progn . ,body))

(def-fwrapper begin-wrapper (&rest args) (pprint args) (call-next-fwrapper))

(fwrap 'begin :pp 'begin-wrapper)

(begin 1 2 3) ;>> ;>> ((BEGIN 1 2 3) NIL) ;=> 3

(macrolet ((foo (n) n)) (begin (foo 8))) ;>> ;>> ((BEGIN (FOO 8)) #<Augmentable INTERPRETER environment 1>) ;=> 8

マクロの環境にもアクセスできるようになっています。

ローカル関数にもラップ

 Allegro CL: Fwrapで凄いのが、ローカル関数もラップできるところ

(defun calfo (n)
  (flet ((masopic (n)
           (* 100 n)))
    (masopic n)))

(def-fwrapper masopic-wrapper (n) (let ((ans (call-next-fwrapper))) (format t "(masopic ~A) => ~A" n ans) ans))

(fwrap '(flet calfo masopic) :w1 'masopic-wrapper)

(calfo 42) ;>> (masopic 42) => 4200 ;=> 4200

(defun calfo (n) (flet ((masopic (n) (* 1000 n))) (masopic n)))

(calfo 42) ;>> (masopic 42) => 42000 ;=> 42000

再定義した場合でもラッパーは残るので、変更は別途fwrap等で行うことになります。

関連

まとめ

 今回は、Allegro CL: Fwrapを紹介してみました。
久し振りに1972年のBBN-LISPのマニュアルのADVICEの説明を読んでみましたが、典型的なADVICEの利用場面として、TRACE、BREAK、BREAKDOWN〈個別の関数の時間の測定〉が挙げられていました。
TRACEは、現在のCommon Lisp処理系でも内部でADVICE機構を利用していることが多いかと思いますが、ラッパーでBREAKやTIMEを仕込むのも使い方によってはデバッグ時に便利かもしれないですね。

comments powered by Disqus