2016-06-25 10 views
2

Ich versuche, ein Makro zu schreiben, das eine Liste von Variablen und eine Stelle des Codes nimmt und stellt sicher, dass Variablen auf ihre ursprünglichen Werte zurück, nachdem der Code ausgeführt wird (Übung 10.6 in Paul Graham's ANSI Common Lisp).Liste gensym Symbol nicht im Inneren Makro bewertet

Allerdings bin ich mir nicht klar, warum meine gensym auswertet, wie ich es an einem Ort erwarten, aber nicht ein anderes ähnliches (Note. Ich weiß, dass es eine bessere Lösung für die Übung, die ich will nur herausfinden, warum der Unterschied in der Bewertung).

Hier ist die erste Definition, wo die lst gensym auf eine Liste innerhalb der die mapcar weitergegeben lambda bewertet:

(defmacro exec-reset-vars-1 (vars body) 
    (let ((lst (gensym))) 
    `(let ((,lst ,(reduce #'(lambda (acc var) `(cons ,(symbol-value var) ,acc)) 
          vars 
          :initial-value nil))) 
     ,@body 
     ,@(mapcar #'(lambda (var) `(setf ,var (car ,lst))) 
        vars)))) 

Aber während es genau funktioniert, wie ich es erwarten, es ist nicht die richtige Lösung für die Übung, weil ich immer das erste Element von lst greifen, wenn ich versuche, Werte zurückzusetzen. Ich möchte wirklich über 2 Listen abbilden. So, jetzt schreibe ich:

(defmacro exec-reset-vars-2 (vars body) 
    (let ((lst (gensym))) 
    `(let ((,lst ,(reduce #'(lambda (acc var) `(cons ,(symbol-value var) ,acc)) 
          vars 
          :initial-value nil))) 
     ,@body 
     ,@(mapcar #'(lambda (var val) `(setf ,var ,val)) 
        vars 
        lst)))) 

Aber jetzt bekomme ich einen Fehler, der sagt #:G3984 keine Liste ist. Wenn ich es durch (symbol-value lst) ersetze, erhalte ich einen Fehler, der besagt, dass Variable keinen Wert hat. Aber warum nicht? Warum hat es einen Wert innerhalb der setf in lambda, aber nicht als ein Argument an mapcar übergeben?

Antwort

6

Bei der Makroexpansionszeit versuchen Sie, den Wert von lst, der zu dieser Zeit ein Symbol ist, zu überlagern. Das ergibt also keinen Sinn.

Der Versuch, einen Symbolwert zu erhalten, macht auch keinen Sinn, da die Bindungen lst lexikalisch sind und symbol-value keine Möglichkeit ist, darauf zuzugreifen. Andere Bindungen sind zu diesem Zeitpunkt nicht verfügbar.

Offensichtlich lst hat einen Wert bei Makroexpansionszeit: ein Symbol. Das siehst du im Lambda.

Sie müssen angeben, welche Werte zur Makroexpansionszeit und welche zur Laufzeit berechnet werden.

Eine Beratung über die Benennung:

  • lst ist ein schlechter Name in Lisp, machen list
  • die Namen verwenden lst keinen Sinn, da sein Wert nicht eine Liste, sondern ein Symbol. Ich würde es list-variable-symbol nennen. Sieht lange aus, oder? Aber es ist viel klarer. Sie würden jetzt, dass es ein Symbol ist, als Name für eine Variable verwendet, die Listen enthält.
+0

Vielen Dank für Ihre Antwort. Um sicherzugehen, dass ich richtig verstehe, hat 'lst' in beiden Fällen den gleichen Wert während der Makroexpansion - ein Symbol. Der Unterschied besteht darin, dass im ersten Fall "(car, lst)" expandiert wird und dann seine expandierte Form während der Laufzeit ausgewertet wird, wobei "lst" eine Liste ist; während in der zweiten, als das Argument zu "mapcar", es während der Makroexpansion ausgewertet wird. Ist das richtig? –

+1

@UnixOne: Nein. Zur Laufzeit gibt es keine 'lst', sondern die Variable mit einem Namen, der zum Makroexpansionszeitpunkt den Wert von' lst' hatte. –

3

Ich bin mir ziemlich sicher, dass Sie es überdenken. Stell dir vor:

(defparameter *global* 5) 
(let ((local 10)) 
    (with-reset-vars (local *global*) 
    (setf *global* 20) 
    (setf local 30) 
    ...)) 

Ich stelle mir die Erweiterung ist so einfach wie:

(defparameter *global* 5) 
(let ((local 10)) 
    (let ((*global* *global*) (local local)) 
    (setf *global* 20) 
    (setf local 30) 
    ...) 
    (print local)) ; prints 10 
(print *global*) ; prints 5 

let hat den Reset auf seine eigenen, so dass Sie sehen, dass das Makro nur sehr einfach sein sollte Schatten Bindungen mit let machen, es sei denn, ich habe den Auftrag missverstanden.

Ihre übermäßig kompliziert Makros macht ziemlich schlechte Dinge. Wie Werte der globalen Symbolkompilierungszeit bekommen, die sie zur Zeit die Funktion zurücksetzten würde, die diese anstelle des Körpers verwendete.

+0

Vielen Dank für Ihre Antwort. Du liegst absolut richtig. Wie ich festgestellt habe, ist mir bewusst, dass es keine gute Lösung für die Übung ist. Ich habe mich nur gewundert, warum das Gensym sie nur an einem Ort bewertet hat, aber nicht am anderen. –

+1

@UnixOne Das ist einfach. In der ersten wird '' car lst-gensym-symbol) 'verwendet, was Ihnen den ersten Teil der' let'-Bindung gibt, die in Runtime gebunden ist, während Sie in der zweiten versuchen, in Makroexpansionszeit, aber in thet abzubilden Phase ist keine Liste, sondern ein Symbolwert. Makros sind im Wesentlichen Codetransformationen und Sie sollten sich nicht auf Variablenbindungen verlassen. – Sylwester