Como posso criar vários desonestos percorrendo uma lista?

11

Estou trabalhando para otimizar minha configuração do emacs, onde posso criar dinamicamente funções interativas para todos os temas que tenho em uma lista.

Abaixo está uma versão simplificada da construção que estou tentando fazer 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))

Mas se eu desenrolar o loop manualmente, ele funcionará:

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

Mas o abaixo não funciona onde passo os nomes dos símbolos (que provavelmente é o que está acontecendo quando o loop se desenrola sozinho). Observe as aspas antes dos argumentos da macro.

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

Atualizar

Graças à ajuda de @wvxvw , finalmente consegui fazer isso funcionar !

Como o @wvxvw sugere, não serei desonesto para gerar lotes para todo e qualquer caso de uso. Este foi um caso de uso especial em que, para um tema chamado XYZ, eu quero gerar um desunião chamado load-theme/XYZque faz o trabalho de

  • Desativando todos os outros temas que podem estar ativos
  • Ligando load-themeparaXYZ
  • Fazendo algumas coisas mais personalizadas relacionadas a esse tema; Passo as configurações personalizadas para cada tema através da lista my/themes.
Kaushal Modi
fonte
1
Coloque tudo defunsdentro de um progn. prognpode ser um formulário de nível superior (no sentido de que tudo o que se aplica aos formulários de nível superior também se aplica ao conteúdo progn). Mas eu questionaria a lógica de criar funções dessa maneira: por que não ter, digamos, uma tabela de has- tas com lambdas como valores?
wvxvw
@wvxvw Não entendi a sugestão. Eu tenho apenas uma macro de criação defun que eu quero chamar várias vezes em um loop. Os exemplos desenrolados manualmente são para mostrar o que funcionou e o que não funcionou enquanto eu tentava descobrir esse problema. Meu objetivo é ter uma lista em vez de uma lista e criar funções interativas para vários temas . Atualmente, o alist consiste apenas em conses, mas pretendo convertê-los em listas com propriedades personalizadas para cada tema.
precisa
Bem, você ligou (my/create-defun name)3 vezes, então você deve definir uma função chamada name3 vezes.
Omar

Respostas:

13

Aqui está uma tentativa de explicação e algumas sugestões.

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

Agora, vamos tentar corrigir isso:

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

Exemplo com a leitura de nomes de funções de uma variável

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

O problema era de um tipo conceitual: as macros são para gerar código quando o ambiente deseja lê-lo. Quando você executa o código você mesmo (como sendo o usuário do seu programa), já é tarde demais para fazê-lo (o ambiente deve saber até lá o que é o programa).


Uma nota marginal: eu recomendaria não juntar várias defuns. A razão é que isso torna a depuração muito mais complicada. A pouca redundância que você tem em definições repetidas compensa muito bem durante a fase de manutenção (e a manutenção é normalmente a fase mais longa da vida útil do programa).

wvxvw
fonte
4
Eu acho que a última nota marginal deve ser em todos os tampões negrito :)
abo-abo
Obrigado! Ótima informação com exemplo. Aceito isso como uma resposta assim que descobrir usando as mapcarlistas. Isso não parece funcionar com meu caso de uso real. Eu vou cavar isso assim que puder.
Kaushal Modi
@kaushalmodi você pode colocar (mapcar (lambda (x) (message "argument: %s" x)) some-alist)para ver qual é o argumento que você recebe e trabalhar a partir daí. Se essa é uma lista associativa, eu imagino que a saída seja algo como argument: (foo . bar), então você pode acessar foousando care barusando cdrfunções.
wvxvw
Sim, fiz o mesmo (só que usei o nthfn em vez de care cadr), mas o sequencepcheck-in está mapcarerrado. Eu estava fornecendo uma lista como entrada, mas ainda assim o mapcar não achou que fosse uma sequência. Se eu fiz (sequencep my-alist), isso foi nulo. Então, eu estou confuso .. Ainda tenho que depurar isso.
Kaushal Modi
@kaushalmodi Imagino duas razões: my-alistfoi nilou você esqueceu (ou acrescentou mais) citações, de modo que isso my-alistera um símbolo ou foi avaliado ainda mais para ser outra coisa. Você provavelmente deseja expandir sua pergunta com o novo código para facilitar a resposta.
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!")

Não exatamente desafia, mas por que não? : P

JAre
fonte
0

Eu tenho o seguinte no meu 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)

Talvez seja um pouco mais complexo do que o necessário (especialmente essa avaliação extra), mas me permite gerar os desajustados necessários para essas propriedades (e incluir documentos com as informações corretas nas strings).

Jonathan Leech-Pepin
fonte