2016-08-19 2 views
2

les gars.Problèmes lors de l'écriture `loop ... collect` dans la macro

Aujourd'hui, je veux écrire une macro sigma pour calculer la somme à partir de l'entrée d'expression flexible.

Le code ci-dessous est écrit cet après-midi. Mais cela ne marche pas suivre mon but.

(defmacro sigma (exp ll) 
    `(+ ,@(loop for i in ll collect 
      (progn (setf (elt exp 1) i) 
        (print exp) 
        exp))) 
) 

>>(pprint (macroexpand-1 '(sigma (+ 1 2) (2 3 4)))) 
>>(+ 2 2) 
    (+ 3 2) 
    (+ 4 2) 
    (+ (+ 4 2) (+ 4 2) (+ 4 2)) 

Je veux que ça fonctionne, mais (+ (+ 2 2) (+ 3 2) (+ 4 2))loop collect me donner la réponse bizarre.

Pourquoi ça marche comme ça? Est-ce que j'ai des méthodes pour résoudre ce problème?

Répondre

3

Si vous voulez une liste fraîchement CONSED, puis copy-list est un moyen:

expressions Emboités BACKQUOTE sont également possibles:

(defmacro sigma ((op arg0 &rest args) ll) 
    (declare (ignore arg0)) 
    `(+ ,@(loop for i in ll collect `(,op ,i ,@args)))) 
6

Vous mutez des données littérales (citées). Si vous acceptez que pendant cette boucle la liste (+ 1 2), liée à exp, est la même dans chaque itération et que vous mutez le deuxième élément dans chaque itération, il est facile d'imaginer qu'une liste qui a recueilli la même liste 3 fois aurait 3 exactement les mêmes éléments avec la toute dernière mutation du deuxième élément.

Ce n'est en aucun cas une fonctionnalité dans les macros. Faire une mutation sur toutes les données citées peut produire un tel résultat. La norme dicte que le résultat ne soit pas défini, donc aucun implémenteur n'a besoin d'y remédier et vous obtenez le comportement inattendu d'autres aspects de l'implémentation particulière.

Un fichier compilé peut joindre toutes les données entre guillemets pour devenir une seule et même personne de telle sorte que '(+ 1 2) autres endroits dans le code peuvent également être affectés par cette macro.

Pour résoudre ce problème il suffit de ne pas muter:

(defmacro sigma ((op r &rest rest) ll) 
    `(+ ,@(loop :for i :in ll 
       :collect (list* op i rest)))) 

(macroexpand-1 '(sigma (+ 1 2) (2 3 4))) 
; ==> (+ (+ 2 2) (+ 3 2) (+ 4 2)) 

La chose à ce sujet est que vous êtes assuré d'avoir au moins deux arguments dans le modèle.

(macroexpand-1 '(sigma (x) (2 3 4))) 
; ==> *** - SIGMA: (X) does not match lambda list element (OP R &REST REST) 
+1

Même si on ne mute il, il collecte le même objet 'exp' à chaque fois dans la boucle. – Barmar

+0

@Barmar Je me souviens d'une fois que j'ai 'NCONC'-ed le même argument deux fois pour obtenir une liste double taille :-) – Sylwester