¿Cómo puedo crear múltiples defuns al recorrer una lista?

11

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 funcionar.

;; 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 a continuación no funciona donde paso los nombres de los símbolos (que probablemente sea lo que sucede cuando el bucle se desenrolla 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 conseguí que esto funcionara !

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

  • Deshabilitar todos los demás temas que puedan estar activos
  • llamar load-themeparaXYZ
  • Haciendo algunas cosas más personalizadas relacionadas con ese tema; Paso la configuración personalizada para cada tema a través de la my/themeslista.
Kaushal Modi
fuente
1
Poner todo defunsdentro de a progn. prognse permite que sea un formulario de nivel superior (en el sentido de que todo lo que se aplica a los formularios de nivel superior también se aplica al contenido de progn). Pero cuestionaría la lógica de crear funciones de esa manera: ¿por qué no tener, por ejemplo, una tabla has con lambdas como valores?
wvxvw
@wvxvw No entendí la sugerencia. Solo tengo una macro de creación defun a la que quiero llamar varias veces en un bucle. Los ejemplos desenrollados manualmente son para mostrar lo que funcionó y lo que no funcionó mientras intentaba resolver este problema. Mi objetivo es tener una lista en lugar de una lista y crear funciones interactivas para varios temas . Actualmente, la lista consta de solo conses pero planeo convertirlos en listas con propiedades personalizadas para cada tema.
Kaushal Modi
Bueno, llamaste (my/create-defun name)3 veces, por lo que deberías terminar definiendo una función llamada name3 veces.
Omar

Respuestas:

13

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 solucionar 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 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 son para generar código cuando el entorno quiere leerlo. Cuando ejecuta el código usted mismo (como usuario de su programa), esto ya es demasiado tarde para hacerlo (el entorno ya debería saber qué es el programa).


Una nota marginal: aconsejaría no agrupar varios 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 paga muy bien durante la fase de mantenimiento (y el mantenimiento suele ser la fase más larga en la vida útil del programa).

wvxvw
fuente
44
Creo que la última nota marginal debería estar en mayúscula negrita :)
abo-abo
¡Gracias! Esa es una gran información con el ejemplo. Aceptaré esto como una respuesta tan pronto como mapcardescubra el uso con listas. Esto no parece funcionar con mi caso de uso real. Investigaré esto tan pronto como pueda.
Kaushal Modi
@kaushalmodi puedes poner (mapcar (lambda (x) (message "argument: %s" x)) some-alist)para ver cuál es el argumento que obtienes, y trabajar desde allí. Si esa es una lista asociativa, me imagino que la salida es algo así argument: (foo . bar), entonces podría acceder foousando cary barusando cdrfunciones.
wvxvw
Sí, hice lo mismo (solo que usé el nthfn en lugar de cary cadr) pero el sequencepcheck-in mapcarerró. Estaba proporcionando una lista como entrada, pero todavía mapcar no creía que fuera una secuencia. Si lo hice (sequencep my-alist), eso no fue nulo. Así que estoy confundido ... todavía tengo que depurar eso.
Kaushal Modi
@kaushalmodi Me imagino dos razones: my-alistfue nilo olvidaste (o agregaste más) comillas para que eso my-alistfuera un símbolo, o fue evaluado aún más como otra cosa. Probablemente desee expandir su pregunta con el nuevo código para que sea más fácil responder.
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!")

No exactamente defunde pero ¿por qué no? :PAG

JAre
fuente
0

Tengo lo siguiente en mi 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 quizás un poco más complejo de lo necesario (especialmente esa evaluación adicional) pero me permite generar las defunciones que necesito para esas propiedades (e incluir cadenas de documentos con la información correcta en las cadenas).

Jonathan Leech-Pepin
fuente