Question

J'ai essayé d'écrire une macro Lisp qui donnerait l'équivalent de ++ dans d'autres langages de programmation pour des raisons sémantiques. J'ai essayé de le faire de différentes manières, mais aucune d'entre elles ne semble fonctionner et toutes sont acceptées par l'interprète. Je ne sais donc pas si j'ai la syntaxe correcte ou non. Mon idée de la façon dont cela serait défini serait

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

mais cela me donne une ERREUR DE TYPE SIMPLE lorsque j'essaie de l'utiliser. Qu'est-ce qui le ferait fonctionner?

Était-ce utile?

La solution

N'oubliez pas qu'une macro retourne une expression à évaluer. Pour ce faire, vous devez lire:

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

Autres conseils

Les deux réponses précédentes fonctionnent, mais elles vous donnent une macro que vous appelez comme

(++ varname)

au lieu de varname ++ ou ++ varname, ce que je suppose que vous voulez. Je ne sais pas si vous pouvez réellement obtenir le premier, mais pour le dernier, vous pouvez faire une macro en lecture. Comme il s’agit de deux caractères, une macro d’expédition est probablement préférable. Non testé, car je n'ai pas de jogging pratique, mais quelque chose comme:

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

doit faire en sorte que ++ var soit lu en tant que (incf var).

Je vous déconseille fortement de créer un alias pour incf. Cela réduirait la lisibilité pour toute autre personne lisant votre code qui doit se demander "qu'est-ce que c'est?" En quoi est-il différent de incf? "

Si vous souhaitez une post-incrémentation simple, essayez ceci:

(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 syntaxe (++ a) est un alias inutile pour (incf a) . Mais supposons que vous vouliez la sémantique de post-incrémentation: récupérer l'ancienne valeur. Dans Common Lisp, cela se fait avec prog1 , comme dans: (prog1 i (incf i)) . Common Lisp ne souffre pas d'ordonnances d'évaluation peu fiables ou ambiguës. L'expression précédente signifie que i est évalué et que la valeur est stockée quelque part, puis que (incf i) est évalué, puis la valeur stockée est renvoyée.

Fabriquer un pincf (post- incf ) parfaitement à l'abri des balles n'est pas tout à fait trivial. (incf i) a la propriété nice que i est évalué une seule fois. Nous aimerions que (pincf i) ait également cette propriété. Et si la macro simple tombe à court:

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

Pour faire cela correctement, nous devons recourir à "l'analyseur de lieu d'affectation" de Lisp. appelé get-setf-expansion pour obtenir des matériaux permettant à notre macro de compiler correctement l'accès:

(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)))))

Quelques tests avec CLISP. (Remarque: les extensions basées sur des éléments de get-setf-expansion peuvent contenir du code spécifique à l'implémentation. Cela ne signifie pas que notre macro n'est pas portable!)

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

Maintenant, voici un cas de test clé. Ici, l'endroit contient un effet secondaire: (aref a (incf i)) . Cela doit être évalué exactement une fois!

[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

Donc, ce qui se passe en premier lieu, c'est que A et (INCF I) sont évalués et deviennent les variables temporaires #: G12680 et #: G12681 . On accède au tableau et la valeur est capturée dans #: G12682 . Nous avons ensuite notre PROG1 qui conserve cette valeur pour le retour. La valeur est incrémentée et stockée dans l'emplacement du tableau via la fonction system :: store de CLISP. Notez que cet appel au magasin utilise les variables temporaires, et non les expressions d'origine A et I . (INCF I) apparaît une seule fois.

Sémantiquement, les préfixes ++ et - dans un langage comme c ++ ou autre sont équivalents incf / decf dans common lisp. Si vous vous en rendez compte et que, comme votre macro (incorrecte), cherchez réellement un changement syntaxique, il vous a déjà été montré comment le faire avec des backticks comme `(incf, x). On vous a même montré comment faire en sorte que le lecteur corrige cela pour se rapprocher de la syntaxe non-lisp. C'est le problème cependant, car aucune de ces choses n'est une bonne idée. En général, le codage non idiomatique pour rendre une langue plus proche d'une autre ne se révèle pas être une si bonne idée.

Cependant, si vous cherchez réellement la sémantique, vous avez déjà les versions de préfixes comme indiqué, mais les versions de postfixes ne seront pas faciles à reproduire syntaxiquement. Vous pouvez le faire avec assez de piratage de lecteur, mais ce ne serait pas beau.

Si c'est ce que vous cherchez, je vous suggérerais a) de coller avec les noms incf / decf car ils sont idiomatiques et fonctionnent bien et b) écrivez des versions post-incf, post-décf, par exemple (defmacro post-incf (x) `(prog1, x (incf, x)) sortes de choses.

Personnellement, je ne vois pas en quoi cela serait particulièrement utile mais ymmv.

Pour le pré-incrément, il y a déjà incf, mais vous pouvez définir le vôtre avec

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

Pour le post-incrément, vous pouvez utiliser ceci (à partir de 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+)

Bien que je garderais certainement à l'esprit les remarques et avances que simon commente dans son post, je pense vraiment que l'approche de user10029 vaut toujours la peine d'essayer Ainsi, juste pour le plaisir, j’ai essayé de combiner cette réponse avec la réponse acceptée pour que l’opérateur ++ x fonctionne (c’est-à-dire, incrémenter la valeur de x dans 1). Essayez-le!

Explication : Un bon vieux SBCL ne compilerait pas sa version, car le symbole '+' doit être défini explicitement dans la table de recherche dispatch-char avec le make-dispatch-macro-character , et la macro est toujours nécessaire pour passer le nom de la variable avant de l’évaluer. Donc, cela devrait faire le travail:

(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|)

Consultez la docstring de | inc-reader | pour un exemple d'utilisation. La documentation (étroitement liée) peut être trouvée ici:

Cette implémentation a pour conséquence que les entrées numériques telles que +123 ne sont plus comprises (le débogueur s’engage avec aucune fonction de dispatch définie pour # \ Newline ), mais une solution de contournement supplémentaire (voire même une évitement) semble raisonnable. : si vous voulez toujours vous en tenir à cela, peut-être que le meilleur choix est de ne pas prendre ++ comme préfixe, mais ## ou toute autre solution plus DSL-ish

acclamations!

Andres

Cela devrait faire l'affaire, mais je ne suis pas un lisp guru.

(defmacro ++ (variable)
  `(setq ,variable (+ ,variable 1)))
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top