Как я могу создать несколько defuns, просматривая список?


11

Я работаю над оптимизацией своего конфига emacs, где я могу динамически создавать интерактивные функции для всех тем, которые есть в списке.

Ниже приведен упрощенный вариант конструкции, которую я пытаюсь заставить работать.

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

Но если я разверну цикл вручную, он будет работать:

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

Но ниже не работает, где я передаю имена символов (что, вероятно, происходит, когда цикл разворачивается сам по себе). Обратите внимание на кавычки перед аргументами макроса.

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

Обновить

Благодаря помощи @wvxvw , я наконец-то заработал !

Как подсказывает @wvxvw, я не буду генерировать пакетные функции для любого и каждого варианта использования. Это был особый случай, когда для темы с именем XYZя хочу создать defun с именем, load-theme/XYZкоторый выполняет работу

  • Отключение всех других тем, которые могут быть активными
  • Призывая load-themeкXYZ
  • Делать еще кое-что, связанное с этой темой; Я передаю пользовательские настройки для каждой темы через my/themesalist.

1
Положите все defunsвнутрь progn. prognразрешено быть формой верхнего уровня (в том смысле, что все, что относится к формам верхнего уровня, относится и к содержимому progn). Но я бы поставил под сомнение обоснованность создания функций таким образом: почему бы не иметь, скажем, таблицу с лямбдами в качестве значений?
wvxvw

@wvxvw Я не понял предложение. У меня есть только один макрос создания defun, который я хочу вызвать несколько раз в цикле. Развернутые вручную примеры показывают, что сработало и не сработало, пока я пытался разобраться в этой проблеме. Моя цель - создать список вместо списка и создать интерактивные функции для различных тем . В настоящее время список состоит только из conses, но я планирую преобразовать их в списки с настраиваемыми свойствами для каждой темы.
Каушал Моди

Ну, вы вызывали (my/create-defun name)3 раза, поэтому вам следует определить функцию, вызываемую name3 раза.
Омар

Ответы:


13

Вот попытка объяснения и некоторые предположения.

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

Теперь давайте попробуем исправить это:

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

Пример с чтением имен функций из переменной

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

Проблема была концептуальной: макросы предназначены для генерации кода, когда среда хочет его прочитать. Когда вы выполняете код самостоятельно (как пользователь вашей программы), уже слишком поздно делать это (к тому времени среда должна знать, что это за программа).


Заметка на полях: я бы посоветовал не смешивать несколько defuns. Причина в том, что это делает отладку намного сложнее. Небольшая избыточность, которую вы имеете в повторяющихся определениях, очень хорошо окупается во время фазы обслуживания (а обслуживание, как правило, является самой длинной фазой в жизни программы).


4
Я думаю, что последняя заметка на полях должна быть написана жирным шрифтом :)
abo-abo

Спасибо! Это отличная информация с примером. Я приму это как ответ, как только выясню, как использовать mapcarс alists. Это не похоже на мой фактический вариант использования. Я буду копаться в этом, как только смогу.
Каушал Моди

@kaushalmodi вы можете указать, (mapcar (lambda (x) (message "argument: %s" x)) some-alist)какой аргумент вы получите, и поработать оттуда. Если это ассоциативный список, я мог бы представить что-то вроде вывода argument: (foo . bar), тогда вы можете получить доступ к fooиспользованию carи barиспользованию cdrфункций.
wvxvw

Да, я сделал то же самое (просто я использовал nthfn вместо carи cadr), но sequencepзарегистрировался с mapcarошибкой. Я предоставлял alist в качестве входных данных, но все же mapcar не думал, что это последовательность. Если бы я сделал (sequencep my-alist), это было не ноль. Так что я в замешательстве .. Я должен все же отладить это.
Каушал Моди

@kaushalmodi Я мог бы представить две причины: my-alistбыли nilили вы забыли (или добавили лишние) кавычки, чтобы они my-alistбыли либо символом, либо оценивались еще дальше как нечто иное. Возможно, вы захотите дополнить свой вопрос новым кодом, чтобы на него было легче ответить.
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!")

Не совсем так, но почему бы и нет? :П


0

У меня есть следующее в моем 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)

Возможно, он немного сложнее, чем нужно (особенно это дополнительный eval), но он позволяет мне генерировать необходимые для этих свойств определения функций (и включать строки строк с правильной информацией в строках).

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.