2008-09-15 16 views
6

Ho tentato di scrivere una macro Lisp in grado di riprodurre l'equivalente di ++ in altri linguaggi di programmazione per ragioni semantiche. Ho tentato di farlo in molti modi diversi, ma nessuno di loro sembra funzionare, e tutti sono accettati dall'interprete, quindi non so se ho la sintassi corretta o meno. La mia idea di come questo possa essere definito sarebbeScrittura di una macro ++ in Common Lisp

(defmacro ++ (variable) 
    (incf variable)) 

ma questo mi dà una semplice-TYPE-errore quando prova ad usarlo. Cosa lo farebbe funzionare?

+0

Non un duplicato, ma correlati: [? Scrivi distruttiva funzione macro o come incf] (http://stackoverflow.com/q/19485964/1281433) –

risposta

17

Ricordare che una macro restituisce un'espressione da valutare. Per fare questo, è necessario backquote:

(defmacro ++ (variable) 
    `(incf ,variable)) 
-2

Questo dovrebbe fare il trucco, ma io non sono un guru Lisp.

(defmacro ++ (variable) 
    `(setq ,variable (+ ,variable 1))) 
+4

Questo non abbastanza lavoro come previsto in tutte le situazioni Come dici tu, "variabile" viene valutata due volte, il che non è ciò che l'utente si aspetta se l'espressione ha effetti collaterali. E.g. guarda come la tua macro espande questa invocazione abbastanza ragionevole: (++ (aref some-vector (++ some-index))) –

+0

Anche questo non funziona se "variabile" è qualcosa di diverso da una variabile (o un simbolo macro), perché 'setq' non funziona con non variabili. Ad esempio, con questo, non puoi fare '(++ (lista auto))'. –

12

Entrambe le risposte precedenti lavori, ma ti danno una macro che si chiama come

(++ varname) 

invece di varname ++ o ++ varname, che ho il sospetto che si desidera. Non so se puoi effettivamente ottenere il primo, ma per quest'ultimo, puoi fare una macro di lettura. Dato che si tratta di due personaggi, una macro di spedizione è probabilmente la migliore. Non testato, dal momento che non ho un lisp a portata di mano in esecuzione, ma qualcosa di simile:

(defun plusplus-reader (stream subchar arg) 
    (declare (ignore subchar arg)) 
    (list 'incf (read stream t nil t))) 
(set-dispatch-macro-character #\+ #\+ #'plusplus-reader) 

dovrebbe rendere ++ var realtà leggere come (incf var).

4

Per la pre-incremento, c'è già incf, ma è possibile definire il proprio con

(define-modify-macro my-incf() 1+) 

In fase di post-incremento, si potrebbe usare questo (da tariffa-utils):

(defmacro define-values-post-modify-macro (name val-vars lambda-list function) 
"Multiple-values variant on define-modify macro, to yield pre-modification values" 
(let ((env (gensym "ENV"))) 
    `(defmacro ,name (,@val-vars ,@lambda-list &environment ,env) 
     (multiple-value-bind (vars vals store-vars writer-form reader-form) 
      (get-setf-expansion `(values ,,@val-vars) ,env) 
     (let ((val-temps (mapcar #'(lambda (temp) (gensym (symbol-name temp))) 
           ',val-vars))) 
      `(let* (,@(mapcar #'list vars vals) 
        ,@store-vars) 
      (multiple-value-bind ,val-temps ,reader-form 
       (multiple-value-setq ,store-vars 
       (,',function ,@val-temps ,,@lambda-list)) 
       ,writer-form 
       (values ,@val-temps)))))))) 

(defmacro define-post-modify-macro (name lambda-list function) 
"Variant on define-modify-macro, to yield pre-modification values" 
`(define-values-post-modify-macro ,name (,(gensym)) ,lambda-list ,function)) 

(define-post-modify-macro post-incf() 1+) 
7

Semanticamente, gli operatori di prefisso ++ e - in un linguaggio come C++ o qualsiasi cosa sono equivalenti incf/decf in comune lisp. Se te ne rendi conto e, come la tua (errata) macro, stai effettivamente cercando una modifica sintattica, ti è già stato mostrato come farlo con i backtick come `(incf, x). Ti è stato anche mostrato come fare in modo che il lettore si attacchi a questo per ottenere qualcosa di più vicino alla sintassi non chiara. Questo è il problema, perché nessuna di queste cose è una buona idea. In generale, la codifica non idiomatica per fare in modo che una lingua assomigli ad un'altra più da vicino non risulta essere una buona idea.

Tuttavia, se si sta effettivamente cercando la semantica, si hanno già le versioni dei prefissi come indicato, ma le versioni postfix non saranno facili da sintonizzare sintatticamente. Potresti farlo con abbastanza ingannare il lettore, ma non sarebbe carino.

Se questo è quello che stai cercando, ti suggerirei di: bastone con i nomi incf/decf poiché sono idiomatici e funzionano bene e b) scrivi post-incf, versioni post-decf, ad esempio (defmacro post- incf (x) `(PROG1, x (incf, x)) i tipi di cose.

Personalmente, non vedo come questo sarebbe particolarmente utile, ma YMMV.

+1

Menzionare 'prog1' è di per sé motivo sufficiente per questo post. Ho usato CL per un lungo periodo e ho dimenticato che è esistito molto tempo fa. – Mars

8

vorrei consigliare vivamente contro fare alias per incf. Ridurrebbe la leggibilità per chiunque altro leggendo il tuo codice che deve chiedersi "cos'è questo? come è diverso da incf?"

Se si desidera un semplice post-incremento, provate questo:

(defmacro post-inc (number &optional (delta 1)) 
    "Returns the current value of number, and afterwards increases it by delta (default 1)." 
    (let ((value (gensym))) 
    `(let ((,value ,number)) 
     (incf ,number ,delta) 
     ,value))) 
+1

Questo però valuta 'number' due volte. [La risposta di Kaz] (http://stackoverflow.com/a/10567794/1281433) mostra come evitare questo. –

8

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.

+1

@JoshuaTaylor Una macro creata da 'define-modify-macro' restituisce il nuovo valore aggiornato. Poiché questo è ciò che "incf" deve restituire, è facile. Non è ovvio se sia altrettanto facile scrivere un 'pincf' con' define-modify-macro', dove il requisito è restituire il valore che era precedentemente nel posto. – Kaz

1

Benche avrei sicuramente tenere a mente le osservazioni e heads-up che simon commenti nel suo post, penso davvero che user10029 s' approccio è ancora la pena di provare, così, quasi per gioco, ho provato combinarlo con la risposta accettata per far funzionare l'operatore ++ x (ovvero, incrementare il valore di x in 1). Provaci!

Spiegazione: Il buon vecchio SBCL non avrebbe compilato la sua versione perché il simbolo '+' deve essere esplicitamente impostato sulla tabella di ricerca dispatch-char con make-dispatch-macro-character, e la macro è ancora necessaria per passare sopra il nome del variabile prima di valutarla. Quindi questo dovrebbe fare il lavoro:

(defmacro increment (variable) 
    "The accepted answer" 
    `(incf ,variable)) 

(make-dispatch-macro-character #\+) ; make the dispatcher grab '+' 

(defun |inc-reader| (stream subchar arg) 
    "sets ++<NUM> as an alias for (incf <NUM>). 
    Example: (setf x 1233.56) =>1233.56 
      ++x => 1234.56 
      x => 1234.56" 
    (declare (ignore subchar arg)) 
    (list 'increment (read stream t nil t))) 

(set-dispatch-macro-character #\+ #\+ #'|inc-reader|) 

s' See |inc-reader|docstring per un esempio di utilizzo.La documentazione (da vicino) correlata può essere trovato qui:

Questa implementazione ha come conseguenza che le entrate numero come 123 non sono più intesa (il debugger salta con no dispatch function defined for #\Newline) ma una soluzione alternativa (o addirittura evitante) sembra ragionevole: se si vuole continuare a farlo, forse la scelta migliore è non prendere ++ come prefisso, ma ## o qualsiasi altra soluzione DSL-ish

evviva!

Andres