Comment puis-je créer plusieurs défuns en parcourant une liste?

11

Je travaille à l' optimisation de ma configuration emacs où je peux créer dynamiquement des fonctions interactives pour tous les thèmes que j'ai dans une liste.

Voici une version simplifiée de la construction que j'essaie de faire fonctionner.

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

Mais si je déroule la boucle manuellement, cela fonctionne:

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

Mais ce qui suit ne fonctionne pas là où je passe les noms de symboles (ce qui se produit probablement lorsque la boucle se déroule d'elle-même). Notez les guillemets avant les arguments de macro.

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

Mise à jour

Grâce à l 'aide de @wvxvw , j'ai finalement réussi à faire en sorte que cela fonctionne !

Comme le suggère @wvxvw, je ne produirai pas de défuns générant des lots pour tous les cas d'utilisation. C'était un cas d'utilisation spécial où pour un thème nommé XYZ, je veux générer un defun appelé load-theme/XYZqui fait le travail de

  • Désactiver tous les autres thèmes qui pourraient être actifs
  • Appel load-themeàXYZ
  • Faire des trucs plus personnalisés liés à ce thème; Je passe les paramètres personnalisés pour chaque thème à travers la liste my/themes.
Kaushal Modi
la source
1
Mettez tout à l' defunsintérieur a progn. prognest autorisé à être un formulaire de niveau supérieur (dans le sens où tout ce qui s'applique aux formulaires de niveau supérieur s'applique également au contenu de progn). Mais je remettrais en question la justification de la création de fonctions de cette manière: pourquoi ne pas avoir, disons, un has-table avec des lambdas comme valeurs?
wvxvw
@wvxvw Je n'ai pas compris la suggestion. J'ai juste une macro de création de defun que je veux appeler plusieurs fois dans une boucle. Les exemples déroulés manuellement sont pour montrer ce qui a fonctionné et ce qui n'a pas fonctionné pendant que j'essayais de comprendre ce problème. Mon objectif est d' avoir une liste au lieu d'une liste et de créer des fonctions interactives pour différents thèmes . Actuellement, la consliste ne contient que des es, mais je prévois de les convertir en listes avec des propriétés personnalisées pour chaque thème.
Kaushal Modi
Eh bien, vous avez appelé (my/create-defun name)3 fois, vous devez donc définir une fonction appelée name3 fois.
Omar

Réponses:

13

Voici une tentative d'explication et quelques suggestions.

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

Maintenant, essayons de résoudre ce problème:

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

Exemple de lecture de noms de fonction à partir d'une variable

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

Le problème était d'ordre conceptuel: les macros sont destinées à générer du code lorsque l'environnement veut le lire. Lorsque vous exécutez le code vous-même (en tant qu'utilisateur de votre programme), il est déjà trop tard pour le faire (l'environnement devrait alors savoir ce qu'est le programme).


Une note marginale: je déconseille de regrouper plusieurs defuns. La raison en est que cela rend le débogage beaucoup plus compliqué. Le peu de redondance que vous avez dans les définitions répétées est très rentable pendant la phase de maintenance (et la maintenance est généralement la phase la plus longue de la durée de vie du programme).

wvxvw
la source
4
Je pense que la dernière note marginale devrait être en majuscules gras :)
abo-abo
Merci! C'est une excellente information avec un exemple. J'accepterai cela comme une réponse dès que je mapcartrouverai à utiliser avec des listes. Cela ne semble pas fonctionner avec mon cas d'utilisation réel. Je creuserai cela dès que possible.
Kaushal Modi
@kaushalmodi vous pouvez mettre (mapcar (lambda (x) (message "argument: %s" x)) some-alist)pour voir quel est l'argument que vous obtenez, et travailler à partir de là. Si c'est une liste associative, j'imagine que la sortie ressemble à quelque chose argument: (foo . bar), alors vous pourriez accéder à l' fooaide caret à l' barutilisation des cdrfonctions.
wvxvw
Oui, j'ai fait la même chose (juste que j'ai utilisé le nthfn au lieu de caret cadr) mais l' sequencepenregistrement a été mapcarerroné. Je fournissais une liste en entrée, mais mapcar ne pensait pas que c'était une séquence. Si je le faisais (sequencep my-alist), ce n'était pas nul. Je suis donc confus .. Je dois encore déboguer cela.
Kaushal Modi
@kaushalmodi J'imagine deux raisons: my-alistétait nilou vous avez oublié (ou ajouté des guillemets supplémentaires) de sorte que my-alistc'était soit un symbole, soit encore évalué pour être autre chose. Vous souhaiterez probablement étendre votre question avec le nouveau code pour faciliter la réponse.
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!")

Pas exactement désamorcé mais pourquoi pas? : P

JAre
la source
0

J'ai les éléments suivants dans mon 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)

Il est peut-être légèrement plus complexe que nécessaire (en particulier cet eval supplémentaire), mais il me permet de générer les défuns dont j'ai besoin pour ces propriétés (et d'inclure des docstrings avec les informations correctes dans les chaînes).

Jonathan Leech-Pepin
la source