Dies ist ein gutes Beispiel für einen Ort, an dem es sich anbietet, einen Zwei-Ebenen-Ansatz zu verwenden, mit einer expliziten, funktionsbasierten Ebene und dann mit einer noch schöneren Makro-Ebene.
Beachten Sie folgendes Listing Common Lisp: Es sieht nur möglich aus Ihrer Frage, die Sie über elisp fragen, in diesem Fall kann so etwas gemacht werden, um zu arbeiten, aber es ist alles viel schmerzhafter.
Zunächst einmal werden wir Rückrufe in einem *callbacks*
genannt alist halten:
(defvar *callbacks* '())
Hier ist eine Funktion, die den alist von Rückrufen
(defun initialize-callbacks()
(setf *callbacks* '())
(values)
Hier ist die Funktion, die einen Rückruf installiert löscht . Dazu wird in der Liste nach einem Rückruf mit dem angegebenen Namen gesucht, und wenn er dann ersetzt wird und andernfalls ein neuer Name erstellt wird. Wie bei allen Funktionen in der Funktionsschicht können wir die Testfunktion angeben, die uns darüber informiert, ob zwei Callback-Namen identisch sind: Standardmäßig ist dies #'eql
, was für Symbole und Zahlen, nicht aber für Strings funktioniert. Symbole sind wahrscheinlich die bessere Wahl für die Namen von Callbacks als Strings, aber wir werden das unten behandeln.
(defun install-callback (name function &key (test #'eql))
(let ((found (assoc name *callbacks* :test test)))
(if found
(setf (cdr found) function)
(push (cons name function) *callbacks*)))
name)
Hier ist eine Funktion einen Rückruf zu finden, die die Funktion Objekt zurückgeben oder nil
, wenn kein Rückruf mit diesem Namen ist.
(defun find-callback (name &key (test #'eql))
(cdr (assoc name *callbacks* :test test)))
Und eine Funktion, um einen benannten Rückruf zu entfernen. Das sagt dir nicht, ob es etwas getan hat: vielleicht sollte es.
(defun remove-callback (name &key (test #'eql))
(setf *callbacks* (delete name *callbacks* :key #'car :test test))
name)
Jetzt kommt die Makroebene. Die Syntax davon wird (define-callback name arguments ...)
sein, also sieht es ein bisschen wie eine Funktionsdefinition aus.
Es gibt drei Dinge über dieses Makro zu wissen.
Es ist ein bisschen clever: weil man bei Makro-Expansionszeit, welche Art von Sache des Name des Callback wissen kann, ist, Sie dann entscheiden können, und es welchen Test zu verwenden, wenn die Callback-Installation, und es macht dies. Wenn der Name ein Symbol ist, wird auch ein block
mit dem Symbol um den Körper der Funktionsdefinition umbrochen, so dass es etwas mehr nach einer durch defun
definierten Funktion riecht: insbesondere können Sie return-from
im Körper verwenden. Dies geschieht nicht, wenn der Name kein Symbol ist.
Es ist nicht ganz clever genug: vor allem geht es nicht mit Docstrings in irgendeiner nützlichen Weise (es sollte sie aus dem Block ziehen, denke ich). Ich bin mir nicht sicher, ob das wichtig ist.
Der Schalter, um den Test zu entscheiden, verwendet Ausdrücke wie '#'eql
, die als (quote (function eql))
liest: das ist Verdrahtung in Funktionen in der Erweiterung zu vermeiden, da Funktionen nicht extern in CL Objekte sind. Aber ich bin mir nicht sicher, ob ich das richtig verstanden habe: Ich denke, was es gibt ist sicher, aber es wird möglicherweise nicht benötigt.
So, hier ist es
(defmacro define-callback (name arguments &body body)
`(install-callback ',name
,(if (symbolp name)
`(lambda ,arguments
(block ,name
,@body))
`(lambda ,arguments
,@body))
:test ,(typecase name
(string '#'string=)
(symbol '#'eql)
(number '#'=)
(t '#'equal))))
Und schließlich sind hier zwei verschiedene Rückrufe definiert ist:
(define-callback "foo" (x)
(+ x 3))
(define-callback foo (x)
(return-from foo (+ x 1)))
Angegebene Symbole in Daten Listen/Conses machen keinen Sinn. '(foo 'bar)' <- warum würdest du 'bar' zitieren? Wenn es sich bei Listen um Daten handelt, müssen Sie möglicherweise die Liste angeben, aber nicht den Inhalt, um eine Auswertung zu verhindern. –
Ja, das Zitat-Symbol sollte außerhalb der gesamten Liste gehen; Die Liste wird jedoch nicht wirklich im Quellcode angezeigt. –