2008-09-15 28 views
6

Ich habe versucht, ein Lisp-Makro zu schreiben, das aus semantischen Gründen das Äquivalent von ++ in anderen Programmiersprachen ausführen würde. Ich habe versucht, dies auf verschiedene Arten zu tun, aber keiner von ihnen scheint zu funktionieren, und alle werden vom Interpreter akzeptiert, also weiß ich nicht, ob ich die richtige Syntax habe oder nicht. Meine Vorstellung davon, wie wäre dies definiert werden würdeSchreiben eines ++ Makros in Common Lisp

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

sein, aber das gibt mir einen SIMPLE-TYPE-Fehler beim Versuch, es zu benutzen. Was würde es funktionieren lassen?

+0

kein Duplikat, aber im Zusammenhang: [? Zerstörend Makro oder Funktion wie incf Schreiben] (http://stackoverflow.com/q/19485964/1281433) –

Antwort

17

Denken Sie daran, dass ein Makro einen auszuwertenden Ausdruck zurückgibt. Um dies zu tun, müssen Sie Backquote:

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

Dies sollte den Trick tun, aber ich bin kein Lisp-Guru.

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

Das wird nicht ganz wie erwartet in allen Situationen. Wie Sie es nennen, wird "Variable" zweimal ausgewertet, was der Benutzer nicht erwartet, wenn der Ausdruck Nebenwirkungen hat. Zum Beispiel schauen, wie das Makro dieses recht vernünftig Aufruf erweitert: (++ (aref etwas-Vektor (++ some-Index))) –

+0

Dies funktioniert auch nicht, wenn, wenn 'variable' ist etwas anderes als eine Variable (oder ein Symbol Makro), weil 'setq' nicht mit Nichtvariablen arbeitet. ZB können Sie '(++ (Autoliste))' nicht tun. –

12

Beide der vorherigen Antworten arbeiten, aber sie geben Ihnen einen Makro, das Sie nennen als

(++ varname) 

statt varname ++ oder ++ varname, die ich vermute, Sie wollen. Ich weiß nicht, ob Sie das erstere bekommen können, aber für das letztere können Sie ein Lesemakro machen. Da es zwei Zeichen ist, ist ein Versandmakro wahrscheinlich am besten. Ungeprüfte, da ich nicht ein handliches Lauf Lisp, aber so etwas wie:

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

sollte ++ var eigentlich lesen als (incf var) machen.

4

Für Prä-Inkrement gibt bereits incf ist, aber Sie können Ihre eigene mit

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

Für Nachinkrement definieren, können Sie diese (von Tarif-utils) verwenden:

(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

Semantisch sind die Präfix-Operatoren ++ und - in einer Sprache wie C++ oder was auch immer, incf/decf im allgemeinen lisp. Wenn du das bemerkst und wie dein (falsches) Makro nach einer syntaktischen Veränderung suchst, hast du bereits gezeigt, wie man es mit Backticks wie `(incf, x) macht. Ihnen wurde sogar gezeigt, wie Sie den Leser dazu bringen können, sich mit der Nicht-Lisp-Syntax vertraut zu machen. Das ist der Haken, denn keines dieser Dinge ist eine gute Idee. Im Allgemeinen erweist sich nicht-idiomatische Codierung, um eine Sprache ähnlicher zu machen, einfach nicht als eine so gute Idee.

Wenn jedoch tatsächlich nach der Semantik gesucht wird, haben Sie bereits die Präfix-Versionen wie angegeben, aber die Postfix-Versionen werden nicht einfach syntaktisch zu sein. Du könntest es mit genug Leser Hacker tun, aber es wäre nicht hübsch.

Wenn das ist, was Sie suchen, würde ich vorschlagen a) bleiben Sie mit incf/decf Namen, da sie idiomatisch sind und gut funktionieren und b) post-incf, post-decf-Versionen schreiben, zB (defmacro post- incf (x) `(prog1, x (incf, x)) Arten von Dingen.

ich persönlich sehe nicht, wie dies besonders nützlich sein würde, aber ymmv.

+1

Erwähnen 'prog1' ist für sich allein Grund genug für diesen Beitrag. Ich habe CL schon lange benutzt und vergessen, dass es schon lange existiert hat. – Mars

8

ich eine dringend raten würde gegen machen alias for incf. Es würde die Lesbarkeit für alle anderen reduzieren, die Ihren Code lesen, die sich fragen müssen: "Was ist das? Wie unterscheidet es sich von inf?"

Wenn Sie eine einfache Nachinkrement wollen, versuchen Sie dies:

(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

Dies wertet 'number' zweimal. [Kaz's Antwort] (http://stackoverflow.com/a/10567794/1281433) zeigt, wie man dies vermeidet. –

8

Die Syntax (++ a) ein nutzloses Alias ​​für (incf a) ist aber, wenn man die Semantik Nachinkrement wollen. Den alten Wert abzurufen. In Common Lisp geschieht dies mit prog1, wie in: (prog1 i (incf i)). Common Lisp leidet nicht unter unzuverlässigen oder mehrdeutigen Evaluationsreihenfolgen.Der obige Ausdruck bedeutet, dass i ausgewertet wird, und der Wert irgendwo gespeichert wird, dann (incf i) ausgewertet wird, und dann wird der verdeckte Wert zurückgegeben

Ein vollständig kugelsicheres pincf (post-incf) ist nicht ganz trivial. (incf i) hat die nette Eigenschaft, dass i nur einmal ausgewertet wird. Wir möchten, dass (pincf i) auch diese Eigenschaft hat. Und so ist die einfache Makro greift zu kurz:

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

dieses Recht zu tun, wir riefen zu Lisp die „Zuordnung Ort Analysator“ get-setf-expansion zurückgreifen müssen, um Materialien zu erhalten, die unsere erlauben Makro den Zugriff richtig zu kompilieren:

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

Ein paar Tests mit CLISP. (Hinweis: Erweiterungen auf Materialien aus get-setf-expansion verlassen Implementierung spezifische Code enthalten Dies bedeutet nicht, unsere Makro nicht tragbar ist.!)

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 

hier nun ein wichtiger Testfall ist. Hier enthält der Platz einen Nebeneffekt: (aref a (incf i)). Dies muss genau einmal ausgewertet werden!

[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 

Also, was zuerst passiert, ist, dass A und (INCF I) ausgewertet werden, und werden die temporären Variablen #:G12680 und #:G12681. Auf das Array wird zugegriffen, und der Wert wird in #:G12682 erfasst. Dann haben wir unsere PROG1, die diesen Wert für die Rückgabe behält. Der Wert wird erhöht und über die CLISP-Funktion system::store in den Array-Speicherort zurückgespeichert. Beachten Sie, dass dieser Geschäftsaufruf die temporären Variablen verwendet und nicht die ursprünglichen Ausdrücke A und I. (INCF I) erscheint nur einmal.

+1

@JoshuaTaylor Ein Makro erstellt von 'define-Modifikations-macro' gibt den neuen, aktualisierte Wert. Da das "incf" zurückkehren muss, ist es einfach. Es ist nicht klar, ob es ist ebenso einfach, ein 'pincf' mit' define-Modifikations-macro' zu schreiben, wo die Anforderung ist, den Wert zurück, der zuvor in dem Ort war. – Kaz

1

Altough ich auf jeden Fall im Auge, die Bemerkungen und Heads-up, dass simon Kommentare in seinem Posten halten würde, ich glaube wirklich, dass user10029 der Ansatz noch ist ein Versuch wert, so, nur zum Spaß, habe ich versucht, um es mit der akzeptierten Antwort zu kombinieren, um den Operator ++ x arbeiten zu lassen (das heißt, den Wert von x in 1 zu erhöhen). Versuche es!

Erklärung: Gute alte SBCL würde seine Version nicht kompilieren, weil das ‚+‘ Symbol explizit mit make-dispatch-macro-character auf der Dispatch-char-Lookup-Tabelle festgelegt werden muss, und das Makro benötigt wird, noch über den Namen der zu übergeben Variable vor der Auswertung. So sollte dies die Arbeit machen:

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

See |inc-reader| ‚s docstring für ein Anwendungsbeispiel.Die (eng) bezogene Dokumentation finden Sie hier:

nicht mehr, dass die Anzahl Einträge wie +123 verstanden Diese Implementierung hat als Konsequenz (der Debugger springt in mit no dispatch function defined for #\Newline), aber weitere Problemumgehung (oder sogar vermieden wird) scheint vernünftig: wenn Sie immer noch mit diesem halten wollen, vielleicht die beste Wahl nicht zu nehmen ++ als Präfix ist, aber ## oder jede andere mehr DSL-ish Lösung

Prost!

Andres