elisp-macros – ¿Cómo puedo crear múltiples defun recorriendo una lista?

Pregunta:

Estoy trabajando en la optimización de mi configuración de emacs donde puedo crear dinámicamente funciones interactivas para todos los temas que tengo en una lista.

A continuación se muestra una versión simplificada de la construcción que estoy tratando de hacer que funcione.

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

Pero si desenrollo el bucle manualmente, funciona:

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

Pero lo siguiente no funciona cuando paso los nombres de los símbolos (que es probablemente lo que está sucediendo cuando el ciclo se desenrolla por sí solo). Tenga en cuenta las comillas antes de los argumentos macro.

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

Actualizar

Gracias a la ayuda de @wvxvw , ¡ finalmente hice que esto funcionara !

Como sugiere @wvxvw, no generaré defunciones por lotes para todos y cada uno de los casos de uso. Este fue un caso de uso especial en el que para un tema llamado XYZ , quiero generar un defun llamado load-theme/XYZ que hace el trabajo de

  • Deshabilitar todos los demás temas que podrían estar activos
  • Llamar a load-theme para XYZ
  • Hacer algunas cosas más personalizadas relacionadas con ese tema; Paso la configuración personalizada para cada tema a través de la lista my/themes .

Respuesta:

Aquí hay un intento de explicación y alguna sugerencia.

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

Ahora, intentemos arreglar esto:

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

Ejemplo con la lectura de nombres de funciones de una 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)))

El problema era de tipo conceptual: las macros sirven para generar código cuando el entorno quiere leerlo. Cuando ejecuta el código usted mismo (como usuario de su programa), ya es demasiado tarde para hacerlo (el entorno debe saber para entonces cuál es el programa).


Una nota al margen: desaconsejaría agrupar varias defuns . La razón es que hace que la depuración sea mucho más complicada. La poca redundancia que tiene en las definiciones repetidas se amortiza muy bien durante la fase de mantenimiento (y el mantenimiento suele ser la fase más larga de la vida útil del programa).

Leave a Comment

Your email address will not be published. Required fields are marked *

web tasarım