Синтаксис (++ a)
- бесполезный псевдоним для (incf a)
. Но предположим, что вам нужна семантика постинкремента: получить старое значение. В Common Lisp это делается с prog1
, как в: (prog1 i (incf i))
. Common Lisp не страдает от ненадежных или неоднозначных оценочных приказов. Предыдущее выражение означает, что i
вычисляется, и значение сохраняется где-то, затем (incf i)
оценивается, и затем возвращается сохраненное значение.
Создание полностью пуленепробиваемого pincf
(post- incf
) не совсем тривиально. (incf i)
имеет свойство nice, которое i
оценивается только один раз. Мы бы хотели, чтобы (pincf i)
также имел это свойство. И вот простой макрос не дотягивает:
(defmacro pincf (place &optional (increment 1))
`(prog1 ,place (incf ,place ,increment))
Чтобы сделать это правильно, мы должны прибегнуть к «анализатору мест назначения» Lisp, который называется get-setf-expansion
, чтобы получить материалы, которые позволяют нашему макросу правильно скомпилировать доступ:
(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))
. Это нужно оценить ровно один раз!
[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
и #:G12681
. Доступ к массиву и его значение заносятся в #:G12682
. Тогда у нас есть PROG1
, который сохраняет это значение для возврата. Значение увеличивается и сохраняется в массиве через функцию CLISP system::store
. Обратите внимание, что этот вызов магазина использует временные переменные, а не исходные выражения A
и I
. (INCF I)
появляется только один раз.