Come posso creare più defuns eseguendo un loop in un elenco?


11

Sto lavorando per ottimizzare la mia configurazione di emacs in cui posso creare dinamicamente funzioni interattive per tutti i temi che ho in un elenco.

Di seguito è una versione semplificata del costrutto che sto cercando di far funzionare.

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

Ma se scarico il ciclo manualmente, funziona:

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

Ma il seguito non funziona dove passo i nomi dei simboli (che è probabilmente ciò che sta accadendo quando il ciclo si svolge da solo). Nota le virgolette prima degli argomenti macro.

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

Aggiornare

Grazie all'aiuto di @wvxvw , ho finalmente funzionato !

Come suggerisce @wvxvw, non genererò batch generatori per ogni singolo caso d'uso. Questo è stato un caso d'uso speciale in cui per un tema chiamato XYZ, voglio generare un defun chiamato load-theme/XYZche fa il lavoro di

  • Disabilitando tutti gli altri temi che potrebbero essere attivi
  • Chiamando load-themeperXYZ
  • Fare alcune altre cose personalizzate relative a quel tema; Passo attraverso le impostazioni personalizzate per ciascun tema attraverso la my/themeslista.

1
Metti tutto defunsdentro a progn. prognè consentito essere un modulo di livello superiore (nel senso che tutto ciò che si applica ai moduli di livello superiore si applica anche al contenuto di progn). Ma metterei in dubbio la logica della creazione di funzioni in questo modo: perché non avere, diciamo, una tabella hash con lambda come valori?
wvxvw,

@wvxvw Non ho capito il suggerimento. Ho solo un defun che crea macro che voglio chiamare più volte in un ciclo. Gli esempi srotolati manualmente mostrano che cosa ha funzionato e che non ha funzionato mentre cercavo di capire questo problema. Il mio obiettivo è quello di avere un elenco anziché un elenco e creare funzioni interattive per vari temi . Attualmente la lista è composta solo da conses, ma ho intenzione di convertirli in elenchi con proprietà personalizzate per ciascun tema.
Kaushal Modi,

Bene, hai chiamato (my/create-defun name)3 volte, quindi dovresti finire per definire una funzione chiamata name3 volte.
Omar,

Risposte:


13

Ecco un tentativo di spiegazione e alcuni suggerimenti.

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

Ora proviamo a risolvere questo problema:

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

Esempio con lettura di nomi di funzioni da una variabile

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

Il problema era di tipo concettuale: le macro servono per generare codice quando l'ambiente vuole leggerlo. Quando esegui tu stesso il codice (come utente del tuo programma), è già troppo tardi per farlo (l'ambiente dovrebbe sapere a quel punto qual è il programma).


Una nota marginale: vorrei sconsigliare di raggruppare diversi defuns. Il motivo è che rende il debuggin molto più complicato. La piccola ridondanza che si ha nelle definizioni ripetute ripaga molto bene durante la fase di manutenzione (e la manutenzione è in genere la fase più lunga della durata del programma).


4
Credo che l'ultima nota a margine dovrebbe essere tutto maiuscolo grassetto :)
abo-ABO

Grazie! Questa è un'ottima informazione con l'esempio. Accetterò questa come risposta non appena avrò scoperto di usarla mapcarcon gli elenchi. Questo non sembra funzionare con il mio caso d'uso reale. Ci approfondirò appena posso.
Kaushal Modi

@kaushalmodi puoi (mapcar (lambda (x) (message "argument: %s" x)) some-alist)vedere qual è l'argomento che ottieni e lavorare da lì. Se quello è un elenco associativo, immagino che l'output sia qualcosa del genere argument: (foo . bar), quindi puoi accedere foousando care barusando le cdrfunzioni.
wvxvw,

Sì, ho fatto lo stesso (solo che ho usato nthfn invece di care cadr) ma ho fatto il sequencepcheck in mapcarerrato. Stavo fornendo un elenco come input, ma mapcar non pensava che fosse una sequenza. Se l'avessi fatto (sequencep my-alist), sarebbe stato nulla. Quindi sono confuso .. Devo ancora eseguire il debug.
Kaushal Modi,

@kaushalmodi Immagino due motivi: my-alistera nilo hai dimenticato (o aggiunto extra) le virgolette in modo che my-alistfosse o un simbolo, o è stato valutato ancora di più per essere qualcos'altro. Probabilmente vuoi espandere la tua domanda con il nuovo codice per rendere più semplice la risposta.
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!")

Non esattamente disertori ma perché no? : P


0

Ho il seguente nel mio 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)

È forse leggermente più complesso del necessario (in particolare quella valutazione aggiuntiva) ma mi consente di generare i defuns di cui ho bisogno per quelle proprietà (e includere docstringhe con le informazioni corrette nelle stringhe).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.