Jak mogę utworzyć wiele odrzutów, przewijając listę?

11

Pracuję nad optymalizacją konfiguracji emacsa, w której mogę dynamicznie tworzyć interaktywne funkcje dla wszystkich motywów, które mam na liście.

Poniżej znajduje się uproszczona wersja konstruktu, który próbuję wykonać.

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

Ale jeśli rozwinę pętlę ręcznie, zadziała:

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

Ale poniżej nie działa, gdy przekazuję nazwy symboli (co prawdopodobnie dzieje się, gdy pętla sama się rozwija). Zwróć uwagę na cudzysłowy przed argumentami makr.

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

Aktualizacja

Dzięki pomocy @wvxvw w końcu udało mi się to uruchomić !

Jak sugeruje @wvxvw, nie będę generować partii generujących odrzucenia dla każdego przypadku użycia. To był szczególny przypadek użycia, w którym dla motywu o nazwie XYZchcę wygenerować wywołanie defun, load-theme/XYZktóre działa

  • Wyłączanie wszystkich innych motywów, które mogą być aktywne
  • wywołanie load-themedlaXYZ
  • Robienie niestandardowych rzeczy związanych z tym tematem; Przechodzę przez niestandardowe ustawienia dla każdego motywu my/themes.
Kaushal Modi
źródło
1
Umieść wszystko w defunsśrodku progn. prognmoże być formularzem najwyższego poziomu (w tym sensie, że wszystko, co dotyczy formularzy najwyższego poziomu, dotyczy również treści progn). Chciałbym jednak zakwestionować uzasadnienie tworzenia funkcji w taki sposób: dlaczego nie mieć, powiedzmy, tabelę z lambdami jako wartościami?
wvxvw
@wvxvw Nie zrozumiałem sugestii. Mam tylko jedno odrzucone tworzenie makra, które chcę wielokrotnie wywoływać w pętli. Ręcznie rozwijane przykłady mają pokazać, co działało, a co nie działało, gdy próbowałem rozwiązać ten problem. Moim celem jest utworzenie listy zamiast listy i tworzenie interaktywnych funkcji dla różnych tematów . Obecnie lista zawiera tylko conse, ale planuję przekonwertować je na listy z niestandardowymi właściwościami dla każdego motywu.
Kaushal Modi
Cóż, zadzwoniłeś (my/create-defun name)3 razy, więc powinieneś zdefiniować funkcję o nazwie name3 razy.
Omar

Odpowiedzi:

13

Oto próba wyjaśnienia i kilka sugestii.

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

Teraz spróbujmy to naprawić:

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

Przykład z odczytaniem nazw funkcji ze zmiennej

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

Problem miał charakter koncepcyjny: makra służą do generowania kodu, gdy środowisko chce go odczytać. Kiedy wykonujesz kod sam (jako użytkownik twojego programu), jest już za późno, aby to zrobić (środowisko powinno wiedzieć, co to jest program).


Uwaga marginalna: odradzałbym łączenie kilku defuns. Powodem jest to, że sprawia, że ​​debugowanie jest o wiele bardziej skomplikowane. Mała nadmiarowość, którą masz w powtarzanych definicjach, bardzo dobrze się opłaca w fazie konserwacji (a konserwacja jest zazwyczaj najdłuższą fazą w czasie trwania programu).

wvxvw
źródło
4
Myślę, że ostatnia marginalna nuta powinna być odważna: :)
abo-abo
Dzięki! To świetna informacja z przykładem. Zaakceptuję to jako odpowiedź, gdy tylko mapcarwymyślę użycie z alistami. To nie wydaje się działać z moim faktycznym przypadkiem użycia. Zagłębię się w to tak szybko, jak to możliwe.
Kaushal Modi
@kaushalmodi możesz umieścić, (mapcar (lambda (x) (message "argument: %s" x)) some-alist)aby zobaczyć, jaki jest argument, i pracować od tego momentu. Jeśli to lista asocjacyjne, to bym sobie wyobrazić wyjście do być coś podobnego argument: (foo . bar), a następnie yo może uzyskać dostęp fooza pomocą cari barprzy użyciu cdrfunkcji.
wvxvw
Tak, zrobiłem to samo (po prostu użyłem nthfn zamiast cari cadr), ale sequencepzameldowałem się mapcarbłędnie. Jako dane wejściowe podawałem alist, ale mapcar nie sądził, że to sekwencja. Jeśli tak (sequencep my-alist), to nie jest zero. Więc jestem zdezorientowany .. Muszę to jeszcze debugować.
Kaushal Modi
@kaushalmodi Mogę sobie wyobrazić dwa powody: my-alistbył nillub zapomniałeś (lub dodany dodatkowy) cytaty, tak aby my-alistbył albo symbol, lub oceniano jeszcze być coś innego. Prawdopodobnie chcesz rozszerzyć swoje pytanie o nowy kod, aby ułatwić udzielenie odpowiedzi.
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!")

Niezupełnie odrzuca, ale dlaczego nie? : P

JAre
źródło
0

Mam w swoim 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)

Jest to być może nieco bardziej skomplikowane niż potrzeba (szczególnie ta dodatkowa ewaluacja), ale pozwala mi generować wyrzuty potrzebne dla tych właściwości (i zawierać ciągi znaków z poprawnymi informacjami).

Jonathan Leech-Pepin
źródło