Синтаксис (++ a)
бесполезная псевдоним для (incf a)
Но предположим, что вы хотите семантику постинкремента:. Получить старое значение. В Common Lisp это делается с помощью prog1
, как в: (prog1 i (incf i))
.Основный Lisp не страдает от ненадежных или неоднозначных оценочных ордеров. Предыдущее выражение означает, что оценивается i
, а значение где-то спрятано, тогда оценивается (incf i)
и то возвращается спрятанное значение.
Изготовление полностью пуленепробиваемого pincf
(пост-incf
) не совсем тривиально. (incf i)
имеет приятное свойство, которое 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)
появляется только один раз.
Не дубликат, но связанные с: [? Пишущие деструктивную макросъемки или функции, как INCF] (http://stackoverflow.com/q/19485964/1281433) –