La sintassi (++ a)
è un alias inutile per (incf a)
Ma supponiamo che si desidera la semantica di post-incremento:. Recuperare il vecchio valore. In Common Lisp, questo viene fatto con prog1
, come in: (prog1 i (incf i))
. Il Common Lisp non presenta ordini di valutazione inaffidabili o ambigui: l'espressione precedente significa che i
viene valutato e il valore è nascosto da qualche parte, quindi viene valutato (incf i)
e quindi viene restituito il valore memorizzato
Realizzare un modello pincf
completamente a prova di proiettile (post-incf
) non è del tutto banale. (incf i)
ha la proprietà nice che i
viene valutata solo una volta. Vorremmo che (pincf i)
abbia anche quella proprietà. E così il semplice macro è inferiore:
(defmacro pincf (place &optional (increment 1))
`(prog1 ,place (incf ,place ,increment))
Per fare questo diritto dobbiamo ricorrere a "posto assegnazione analizzatore" di Lisp chiamato get-setf-expansion
di ottenere materiali che permettono la nostra macro per compilare l'accesso correttamente:
(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)))))
Alcuni test con CLISP. (Nota: espansioni basandosi su materiali da get-setf-expansion
possono contenere codice specifico dell'implementazione Questo non significa che la nostra macro non è portabile.!)
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
Ora qui è un banco di prova fondamentale. Qui, il posto contiene un effetto collaterale: (aref a (incf i))
. Questo deve essere valutato esattamente una volta!
[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
Quindi ciò che accade prima è che A
e (INCF I)
vengono valutati, e diventare le variabili temporanee #:G12680
e #:G12681
. Si accede alla matrice e il valore viene acquisito in #:G12682
. Quindi abbiamo il nostro PROG1
che mantiene quel valore per il reso. Il valore viene incrementato e memorizzato nella posizione dell'array tramite la funzione system::store
di CLISP. Si noti che questa chiamata al negozio utilizza le variabili temporanee, non le espressioni originali A
e I
. (INCF I)
viene visualizzato solo una volta.
Non un duplicato, ma correlati: [? Scrivi distruttiva funzione macro o come incf] (http://stackoverflow.com/q/19485964/1281433) –