構文(++ a)
は の役に立たないエイリアスです(incf a)
。しかし、ポストインクリメントのセマンティクスが必要だとします: 古い値を取得します。Common Lisp では、これは のように で行われprog1
ます(prog1 i (incf i))
。Common Lisp は、信頼できない、またはあいまいな評価順序に悩まされることはありません。前の式は、i
が評価され、値がどこかにスタッシュ(incf i)
され、評価され、スタッシュされた値が返されることを意味します。
完全な防弾pincf
(post- incf
) を作成することは、まったく簡単なことではありません。一度だけ評価される(incf i)
ナイスプロパティを持っています。私たちもその財産を持ちi
たいと思っています。(pincf i)
したがって、単純なマクロでは不十分です。
(defmacro pincf (place &optional (increment 1))
`(prog1 ,place (incf ,place ,increment))
get-setf-expansion
これを正しく行うには、マクロがアクセスを適切にコンパイルできるようにするための材料を取得するために呼び出される Lisp の「割り当て場所アナライザー」に頼る必要があります。
(defmacro pincf (place-expression &optional (increment 1) &environment env)
(multiple-value-bind (temp-syms val-forms
store-vars store-form access-form)
(get-setf-expansion place-expression env)
(when (cdr store-vars)
(error "pincf: sorry, cannot increment multiple-value place. extend me!"))
`(multiple-value-bind (,@temp-syms) (values ,@val-forms)
(let ((,(car store-vars) ,access-form))
(prog1 ,(car store-vars)
(incf ,(car store-vars) ,increment)
,store-form)))))
CLISP を使用したいくつかのテスト。(注: からのマテリアルに依存する展開にget-setf-expansion
は、実装固有のコードが含まれる場合があります。これは、マクロが移植可能でないという意味ではありません!)
8]> (macroexpand `(pincf simple))
(LET* ((#:VALUES-12672 (MULTIPLE-VALUE-LIST (VALUES))))
(LET ((#:NEW-12671 SIMPLE))
(PROG1 #:NEW-12671 (INCF #:NEW-12671 1) (SETQ SIMPLE #:NEW-12671)))) ;
T
[9]> (macroexpand `(pincf (fifth list)))
(LET*
((#:VALUES-12675 (MULTIPLE-VALUE-LIST (VALUES LIST)))
(#:G12673 (POP #:VALUES-12675)))
(LET ((#:G12674 (FIFTH #:G12673)))
(PROG1 #:G12674 (INCF #:G12674 1)
(SYSTEM::%RPLACA (CDDDDR #:G12673) #:G12674)))) ;
T
[10]> (macroexpand `(pincf (aref a 42)))
(LET*
((#:VALUES-12679 (MULTIPLE-VALUE-LIST (VALUES A 42)))
(#:G12676 (POP #:VALUES-12679)) (#:G12677 (POP #:VALUES-12679)))
(LET ((#:G12678 (AREF #:G12676 #:G12677)))
(PROG1 #:G12678 (INCF #:G12678 1)
(SYSTEM::STORE #:G12676 #:G12677 #:G12678)))) ;
T
次に、重要なテスト ケースを示します。ここでは、場所に副作用が含まれています: (aref a (incf i))
. これは 1 回だけ評価する必要があります。
[11]> (macroexpand `(pincf (aref a (incf i))))
(LET*
((#:VALUES-12683 (MULTIPLE-VALUE-LIST (VALUES A (INCF I))))
(#:G12680 (POP #:VALUES-12683)) (#:G12681 (POP #:VALUES-12683)))
(LET ((#:G12682 (AREF #:G12680 #:G12681)))
(PROG1 #:G12682 (INCF #:G12682 1)
(SYSTEM::STORE #:G12680 #:G12681 #:G12682)))) ;
T
最初にA
と(INCF I)
が評価され、一時変数#:G12680
and になり#:G12681
ます。配列がアクセスされ、値が に取り込まれ#:G12682
ます。次に、PROG1
その値を保持して返すものがあります。system::store
値がインクリメントされ、CLISP の関数を介して配列の場所に格納されます。この store 呼び出しは、元の式A
andではなく、一時変数を使用することに注意してくださいI
。 (INCF I)
一度だけ登場。