Wie kann ich mehrere Defuns erstellen, indem ich eine Liste durchlaufe?

11

Ich arbeite an der Optimierung meiner Emacs-Konfiguration, in der ich dynamisch interaktive Funktionen für alle Themen erstellen kann, die ich in einer Liste habe.

Unten finden Sie eine vereinfachte Version des Konstrukts, mit dem ich arbeiten möchte.

;; List containing names of functions that I want to create
(setq my/defun-list '(zz-abc
                      zz-def
                      zz-ghi))

;; Elisp macro to create an interactive defun whose name
;; is passed as the macro argument
(defmacro my/create-defun (defun-name)
  `(defun ,defun-name ()
     (interactive)
     (let ((fn-name (symbol-name ',defun-name)))
       (message "Testing creation of function %s" fn-name))))

;; Loop to call the above macro for each element in the list
;; DOES *NOT* WORK
(dolist (name my/defun-list)
  (my/create-defun name))

Aber wenn ich die Schleife manuell abwickle, funktioniert es:

;; WORKS
(my/create-defun zz-abc)
(my/create-defun zz-def)
(my/create-defun zz-ghi)

Aber das Folgende funktioniert nicht, wenn ich die Symbolnamen übergebe (was wahrscheinlich passiert, wenn sich die Schleife von selbst abwickelt). Beachten Sie die Anführungszeichen vor den Makroargumenten.

;; DOES *NOT* WORK
(my/create-defun 'zz-abc)
(my/create-defun 'zz-def)
(my/create-defun 'zz-ghi)

Aktualisieren

Dank der Hilfe von @wvxvw habe ich es endlich geschafft !

Wie @wvxvw vorschlägt, werde ich keine Batch-generierenden Defuns für jeden Anwendungsfall erstellen. Dies war ein spezieller Anwendungsfall, bei dem XYZich für ein Thema mit dem Namen einen Defun generieren möchte load-theme/XYZ, der die Aufgabe erfüllt

  • Deaktivieren aller anderen Themen, die möglicherweise aktiv sind
  • Fordern load-themenachXYZ
  • Weitere benutzerdefinierte Aufgaben im Zusammenhang mit diesem Thema ausführen; Ich übergebe die benutzerdefinierten Einstellungen für jedes Thema über die my/themesListe.
Kaushal Modi
quelle
1
Legen Sie alles defunsin ein progn. progndarf ein Formular der obersten Ebene sein (in dem Sinne, dass alles, was für Formulare der obersten Ebene gilt, auch für den Inhalt von gilt progn). Aber ich würde die Gründe für die Erstellung von Funktionen so in Frage stellen: Warum nicht beispielsweise eine Has-Tabelle mit Lambdas als Werten?
wvxvw
@wvxvw Ich habe den Vorschlag nicht verstanden. Ich habe nur ein Defun-Erstellungsmakro, das ich mehrmals in einer Schleife aufrufen möchte. Die manuell abgewickelten Beispiele sollen zeigen, was funktioniert hat und was nicht, während ich versucht habe, dieses Problem herauszufinden. Mein Ziel ist es , eine Liste anstelle einer Liste zu erstellen und interaktive Funktionen für verschiedene Themen zu erstellen . Derzeit besteht die Liste nur aus conses, aber ich plane, diese in Listen mit benutzerdefinierten Eigenschaften für jedes Thema zu konvertieren.
Kaushal Modi
Nun, Sie haben (my/create-defun name)dreimal aufgerufen , also sollten Sie eine namedreimal definierte Funktion definieren .
Omar

Antworten:

13

Hier ist ein Erklärungsversuch und ein Vorschlag.

(defmacro my/create-defun (defun-name)
  `(defun ,defun-name ()
     (interactive)
     (let ((fn-name (symbol-name ',defun-name)))
       (message "Testing creation of function %s" fn-name))))

(dolist (name my/defun-list)
  ;; Macros are meant to create code, not execute it.  Think
  ;; about simply substituting the contents of your macro here
  ;; what would you expect it to do?
  (my/create-defun name))

(dolist (name my/defun-list)
  ;; This is not something a compiler (or interpreter)
  ;; can work with, it needs all sources of the code it
  ;; is going to execute
  (defun defun-name ()
    (interactive)
    (let ((fn-name (symbol-name 'defun-name)))
      (message "Testing creation of function %s" fn-name))))

;; This works because you, indeed created three defuns
(my/create-defun zz-abc)
(my/create-defun zz-def)
(my/create-defun zz-ghi)

;; This doesn't work because `defun' macro expect the name of
;; the function to be a symbol (but you are giving it a list
;; `(quote zz-abc)'.
(my/create-defun 'zz-abc)
(my/create-defun 'zz-def)
(my/create-defun 'zz-ghi)

Versuchen wir nun, dies zu beheben:

;; Rewriting the original macro as a function and using a
;; macro to collect the generated forms gives:
(defun my/create-defun (defun-name)
  `(defun ,defun-name ()
     (interactive)
     (let ((fn-name (symbol-name ',defun-name)))
       (message "Testing creation of function %s" fn-name))))

(defmacro my/create-defuns (defuns)
  `(progn ,@(mapcar 'my/create-defun defuns)))

(macroexpand '(my/create-defuns (zz-abc zz-def zz-ghi)))
;; Just to make sure
(progn
  (defun zz-abc nil
    (interactive)
    (let ((fn-name (symbol-name (quote zz-abc))))
      (message "Testing creation of function %s" fn-name)))
  (defun zz-def nil
    (interactive)
    (let ((fn-name (symbol-name (quote zz-def))))
      (message "Testing creation of function %s" fn-name)))
  (defun zz-ghi nil
    (interactive)
    (let ((fn-name (symbol-name (quote zz-ghi))))
      (message "Testing creation of function %s" fn-name))))

Beispiel mit dem Lesen von Funktionsnamen aus einer Variablen

(defvar my/functions '((func-1 . 1) (func-2 . 2) (func-3 . 3)))

(defun my/create-defun-n (defun-name n)
  `(defun ,defun-name ()
     (message "function: %s, n %d" ',defun-name ,n)))

(defmacro my/create-defuns-var ()
  `(progn ,@(mapcar
             (lambda (x) (my/create-defun-n (car x) (cdr x)))
             my/functions)))

(macroexpand '(my/create-defuns-var))
(progn
  (defun func-1 nil (message "function: %s, n %d" (quote func-1) 1))
  (defun func-2 nil (message "function: %s, n %d" (quote func-2) 2))
  (defun func-3 nil (message "function: %s, n %d" (quote func-3) 3)))

Das Problem war konzeptioneller Art: Makros dienen zum Generieren von Code, wenn die Umgebung ihn lesen möchte. Wenn Sie den Code selbst ausführen (als Benutzer Ihres Programms), ist dies bereits zu spät (die Umgebung sollte bis dahin wissen, was das Programm ist).


Eine Randnotiz: Ich würde davon abraten, mehrere zusammenzufassen defuns. Der Grund ist, dass das Debuggen dadurch viel komplizierter wird. Die geringe Redundanz, die Sie bei wiederholten Definitionen haben, zahlt sich während der Wartungsphase sehr gut aus (und die Wartung ist normalerweise die längste Phase in der Programmlebensdauer).

wvxvw
quelle
4
Ich denke, die letzte Randnotiz sollte in allen Fettdrucken stehen :)
abo-abo
Vielen Dank! Das sind großartige Informationen mit Beispiel. Ich werde dies als Antwort akzeptieren, sobald ich herausgefunden habe, wie ich es mapcarmit Alisten verwende. Dies scheint mit meinem tatsächlichen Anwendungsfall nicht zu funktionieren. Ich werde mich so schnell wie möglich damit befassen.
Kaushal Modi
@kaushalmodi Sie können setzen (mapcar (lambda (x) (message "argument: %s" x)) some-alist), um zu sehen, was das Argument ist, das Sie bekommen, und von dort aus arbeiten. Wenn das eine assoziative Liste ist, würde ich mir vorstellen, dass die Ausgabe so etwas wie ist argument: (foo . bar), dann könnten Sie foomit carund barmit cdrFunktionen darauf zugreifen .
wvxvw
Ja, ich habe das Gleiche getan (nur dass ich das nthfn anstelle von carund verwendet habe cadr), aber das sequencepEinchecken ist mapcarfehlgeschlagen. Ich gab eine Liste als Eingabe an, aber Mapcar glaubte immer noch nicht, dass dies eine Sequenz war. Wenn ich das tat (sequencep my-alist), war das nicht Null. Also bin ich verwirrt. Ich muss das noch debuggen.
Kaushal Modi
@kaushalmodi Ich würde mir zwei Gründe vorstellen: my-alistwar niloder du hast Anführungszeichen vergessen (oder hinzugefügt), so dass dies my-alistentweder ein Symbol war oder noch weiter bewertet wurde, um etwas anderes zu sein. Sie möchten Ihre Frage wahrscheinlich mit dem neuen Code erweitern, um die Beantwortung zu vereinfachen.
wvxvw
2
(dolist (fun '(foo bar baz))
  (defalias fun (lambda (a)
                  "I'm a function defined in `dolist'!"
                  (interactive)
                  (message a))))
(bar "See? No macro!")

Nicht gerade defuns, aber warum nicht? : P.

JAre
quelle
0

Ich habe folgendes in meinem Init:

(my/work-properties '("hostname" "username" "location"))

(defmacro jlp/make-def (name props doc &rest body)
  "Shortcut to programatically create new functions"
  (let ((funsymbol (intern name)))
    `(defun ,funsymbol ,props ,doc ,@body)))

(defun my/make-work-props (properties)
  "Create functions to retrieve properties from Org headlines."
  (dolist (prop properties)
    (let ((funsym   (format "my/org-get-%s" prop))
          (property (intern (format ":%s" (upcase prop))))
          (doc      (format "Retrieves `%s' from current headline"
                            (upcase prop)))
          (err (format "%s is not set" (capitalize prop))))
      (eval
       `(jlp/make-def ,funsym
                      ()
                      ,doc
                      (interactive)
                      (let ((x (or
                                (save-excursion
                                  (org-back-to-heading)
                                  (org-element-property
                                   ,property
                                   (org-element-at-point)))
                                (user-error ,err))))
                        (message "%s" x)
                         (kill-new x)))))))

(my/make-work-props my/org-work-properties)

Es ist vielleicht etwas komplexer als nötig (insbesondere diese zusätzliche Auswertung), aber es ermöglicht mir, die Defuns zu generieren, die ich für diese Eigenschaften benötige (und Docstrings mit den richtigen Informationen in die Strings aufzunehmen).

Jonathan Leech-Pepin
quelle