minf/maxf
一つ前の記事では、以下のようなmaxf及びminfというマクロを定義して使っていた。
;;; 定義 (defmacro maxf (a &rest as) `(setf ,a (max ,@as))) (defmacro minf (a &rest as) `(setf ,a (min ,@as))) ;;; 使用例 ;; 「aの値が20よりも小さい場合は、aの値を20に設定する」 (defvar a 10) ;; 条件分岐を使った場合 ※ 比較したい値が3個以上になると複雑になる (if (< a 20) (setf a 20)) ;; setfとmaxを明示的に組み合わせた場合 (setf a (max a 20)) ;; maxfを使った場合 (maxf a 20)
上ではminfなどの定義にdefmacroを直接使っているが、define-modify-macroという、より目的に沿ったマクロもあるらしい。
;; 上の定義と機能的に等価 (define-modify-macro maxf (&rest as) max) (define-modify-macro mixf (&rest as) min)
汎変数関連のマクロはあまり使わないから忘れていた。
これくらいの定義なら、defmacroを用いた方が直截的で良いような気もするが、一応覚えておこう。
第三引数にlambda式を使う場合
ちなみに、define-modify-macroの第三引数は、lambda式でも良いらしい*1ので、引数に対して少し複雑な操作を行いたい場合はdefine-modify-macroの方が便利な時もあるかもしれない。
以下一例:
;;;;;;;;;;;;; ;; [マクロの動作] ;; リストの二番目の要素にvalをプラスする ;; ただし、valが数値ではない場合は、何も行わない ;; ;; 以下はdefmacroを用いた間違った定義 ;; valに副作用がある式を渡した場合、二重に評価されてしまう危険性がある (defmacro second+-if-number (lst val) `(progn (when (numberp ,val) (incf (second ,lst) ,val)) ,lst)) ;; 動作例 > (defparameter a '(0 0 0)) > (defparameter b 1) > (second+-if-number a b) --> (0 1 0) > a --> (0 1 0) > (second+-if-number a 'symbol) --> (0 1 0) ;; 副作用がある式を渡した場合は、おかしな結果となる > (setf a '(0 0 0) b 1) > (second+-if-number a (incf b)) --> (0 3 0) ; 期待した値は、(0 2 0) > b --> 3 ; (incf b)がマクロ内部で二回評価されているため+2されている ;;;;;;;;;;;;;;; ;; define-modify-macroを用いた定義 ;; こっちのメリットは二つ ;; - 二重評価の心配がない ;; - 変数のquote/unquoteを気にしなくて済む (define-modify-macro second+-if-number (val) (lambda (lst val) (when (numberp val) (incf (second lst) val)) lst)) ;; 動作例 > (setf a '(0 0 0) b 1) > (second+-if-number a (incf b)) --> (0 2 0) ; 期待通り > b --> 2 ; こっちも