Domanda

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

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

ma questo mi dà un SIMPLE-TYPE-ERROR quando provo a usarlo.Cosa lo farebbe funzionare?

È stato utile?

Soluzione

Ricordare che una macro restituisce un'espressione da valutare.Per fare ciò, devi citare in retrospettiva:

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

Altri suggerimenti

Entrambe le risposte precedenti funzionano, ma ti danno una macro che chiami as

(++ varname)

invece di varname++ o ++varname, che sospetto tu voglia.Non so se riesci effettivamente a ottenere il primo, ma per il secondo puoi eseguire una macro di lettura.Dato che sono due caratteri, una macro di invio è probabilmente la soluzione migliore.Non testato, dal momento che non ho un pratico lisp in esecuzione, ma qualcosa del tipo:

(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 in realtà Leggere come (incf var).

Sconsiglio vivamente di creare un alias per incf.Ridurrebbe la leggibilità per chiunque altro legga il tuo codice e debba chiedersi "cos'è questo?in cosa differisce da incf?"

Se vuoi un semplice post-incremento, prova 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)))

La sintassi (++ a) è un alias inutile per (incf a).Ma supponiamo di volere la semantica del post-incremento:recuperare il vecchio valore.In Common Lisp, questo è fatto prog1, come in: (prog1 i (incf i)).Common Lisp non soffre di ordini di valutazione inaffidabili o ambigui.L'espressione precedente significa questo i viene valutato e il valore viene quindi nascosto da qualche parte (incf i) viene valutato, quindi viene restituito il valore nascosto.

Renderlo completamente a prova di proiettile pincf (inviare-incf) non è del tutto banale. (incf i) ha la bella proprietà che i viene valutato una sola volta.Vorremmo (pincf i) avere anche quella proprietà.E quindi la semplice macro non è all'altezza:

(defmacro pincf (place &optional (increment 1))
  `(prog1 ,place (incf ,place ,increment))

Per fare ciò dobbiamo ricorrere all'"analizzatore di posti di assegnazione" del Lisp chiamato get-setf-expansion per ottenere materiali che permettano alla nostra macro di compilare correttamente l'accesso:

(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 basate su materiali da get-setf-expansion può contenere codice specifico dell'implementazione.Questo non significa che la nostra macro non sia portatile!)

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 ecco un caso di prova chiave.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 quello che succede prima è questo A E (INCF I) vengono valutati e diventano variabili temporanee #:G12680 E #:G12681.Si accede all'array e il valore viene acquisito #:G12682.Poi abbiamo il nostro PROG1 che mantiene quel valore per il rendimento.Il valore viene incrementato e memorizzato nuovamente nella posizione dell'array tramite CLISP system::store funzione.Tieni presente che questa chiamata allo store utilizza le variabili temporanee, non le espressioni originali A E I. (INCF I) appare solo una volta.

Semanticamente, gli operatori del prefisso ++ e -- in un linguaggio come c++ o qualsiasi altro sono equivalenti incf/decf nel lisp comune.Se te ne rendi conto e, come la tua macro (errata), stai effettivamente cercando un cambiamento sintattico, allora ti è già stato mostrato come farlo con gli apici inversi come `(incf ,x).Ti è stato anche mostrato come fare in modo che il lettore aggiri questo problema per ottenere qualcosa di più vicino alla sintassi non lisp.Questo è il problema però, poiché nessuna di queste cose è una buona idea.In generale, la codifica non idiomatica per rendere una lingua più simile ad un'altra non si rivela una buona idea.

Tuttavia, se stai effettivamente cercando la semantica, hai già le versioni del prefisso come notato, ma le versioni del suffisso non saranno facili da abbinare sintatticamente.Potresti farlo con abbastanza hackeraggio da parte dei lettori, ma non sarebbe carino.

Se è quello che stai cercando, suggerirei di a) attenersi ai nomi incf/decf poiché sono idiomatici e funzionano bene e b) scrivere versioni post-incf, post-decf, ad esempio (defmacro post-incf (x) "(prog1 ,x (incf ,x)) tipi di cose.

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

Per il pre-incremento c'è già incf, ma puoi definirne uno tuo con

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

Per il post-incremento, potresti usare questo (da fare-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+)

Anche se terrò sicuramente a mente le osservazioni e gli avvertimenti simone commenti nel suo post, lo penso davvero utente10029vale comunque la pena provare l'approccio di, quindi, solo per divertimento, ho provato a combinarlo con la risposta accettata per rendere il ++x lavoro dell'operatore (ovvero, incrementare il valore di x in 1).Provaci!

Spiegazione:Il buon vecchio SBCL non compilerebbe la sua versione perché il simbolo '+' deve essere impostato esplicitamente nella tabella di ricerca dei caratteri di invio con make-dispatch-macro-character, ed è ancora necessario che la macro passi il nome della 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|)

Vedere |inc-reader|'S docstring per un esempio di utilizzo.La documentazione (strettamente) correlata può essere trovata qui:

Questa implementazione ha come conseguenza che le voci numeriche come +123 non vengono più comprese (il debugger interviene con no dispatch function defined for #\Newline) ma un'ulteriore soluzione alternativa (o addirittura evitarla) sembra ragionevole:se vuoi comunque mantenerlo, forse la scelta migliore è non prendere ++ come prefisso, ma ## o qualsiasi altra soluzione più simile a DSL

saluti!

Andres

Questo dovrebbe funzionare, tuttavia non sono un guru della lisp.

(defmacro ++ (variable)
  `(setq ,variable (+ ,variable 1)))
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top