Як я можу створити кілька помилок, переглянувши список?


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я хочу генерувати розгортку, яку називають, load-theme/XYZяка виконує завдання

  • Вимкнення всіх інших тем, які можуть бути активними
  • виклик load-themeдляXYZ
  • Робити ще деякі спеціальні речі, пов’язані з цією темою; Я передаю спеціальні налаштування для кожної теми через список my/themes.

1
Покладіть все defunsвсередину progn. prognможе бути формою найвищого рівня (у тому сенсі, що все, що стосується форм вищого рівня, теж стосується вмісту progn). Але я б поставив під сумнів обґрунтування створення функцій таким чином: чому б не мати, скажімо, таблицю з таблицями з лямбдазами як значення?
wvxvw

@wvxvw Я не зрозумів пропозицію. У мене є лише одне налаштування створення макросу, яке я хочу викликати кілька разів у циклі. Приклади, що розгортаються вручну, показують, що працювало, а що не працювало, коли я намагався з'ясувати цю проблему. Моя мета - скласти список замість списку та створити інтерактивні функції для різних тем . В даний час список складається лише з 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
Я думаю, що остання маргінальна нота повинна бути у всіх жирних шапках :)
абобо Або

Дякую! Це чудова інформація з прикладом. Я прийму це як відповідь, як тільки mapcarрозберусь із аллістами. Схоже, це не працює з моїм фактичним випадком використання. Я розберуся в цьому, як тільки зможу.
Каушал Моді

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

Так, я зробив те саме (тільки що я використав nthfn замість carі cadr), але sequencepперевірив mapcarпомилку. Я надавав аліст як вхід, але все ще 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!")

Не зовсім схильні, але чому б і ні? : P


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)

Це, можливо, трохи складніше, ніж потрібно (особливо, що це зайвий евал), але це дозволяє мені генерувати потрібні для цього властивості (і включати в рядки документи з правильною інформацією).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.