11

I am working on optimizing my emacs config where I can dynamically create interactive functions for all themes I have in a list.

Below is a simplified version of the construct I am trying to make work.

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

But if I unroll the loop manually, it works:

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

But the below does not work where I pass in the symbol names (which is probably what's happening when the loop unrolls by itself). Note the quotes before the macro arguments.

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

Update

Thanks to @wvxvw's help, I finally got this working!

As @wvxvw suggests, I will not be batch-generating defuns for any and every use case. This was a special use case where for a theme named XYZ, I want to generate a defun called load-theme/XYZ that does the job of

  • Disabling all other themes that might be active
  • Calling load-theme for XYZ
  • Doing some more custom stuff related to that theme; I pass in the custom settings for each theme through the my/themes alist.
  • 1
    Put all defuns inside a progn. progn is allowed to be a top-level form (in the sense that everything that applies to top-level forms applies to the contents of progn too). But I would question the rationale of creating functions in such way: why not have, say, a has-table with lambdas as values? – wvxvw Mar 18 '15 at 21:49
  • @wvxvw I didn't understand the suggestion. I have just one defun creating macro which I want to call multiple times in a loop. The manually unrolled examples are to show what worked and didn't work while I was trying to figure out this issue. My aim is to have an alist instead of a list and create interactive functions for various themes. Currently the alist consists of only conses but I plan to convert those to lists with custom properties for each theme. – Kaushal Modi Mar 18 '15 at 21:55
  • Well, you called (my/create-defun name) 3 times, so you should wind up defining a function called name 3 times. – Omar Mar 18 '15 at 22:18
13

Here's an attempt at explaining and some suggestion.

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

Now, let's try to fix this:

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

Example with reading function names from a 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)))

The problem was of a conceptual kind: macros are for generating code when the environment wants to read it. When you execute the code yourself (as being the user of your program) this is already too late to do it (the environment should know by then what the program is).


A marginal note: I would advise against lumping together several defuns. The reason is that it makes debuggin a lot more complicated. The little redundancy you have in repeated definitions pays off very well during maintenance phase (and maintenance is typically the longest phase in program's life time).

  • 4
    I think the last marginal note should be in all bold caps:) – abo-abo Mar 19 '15 at 7:27
  • Thanks! That's great information with example. I'll accept this as an answer as soon as I figure out using mapcar with alists. This doesn't seem to work with my actual use case. I'll dig into this as soon as I can. – Kaushal Modi Mar 19 '15 at 17:18
  • @kaushalmodi you can put (mapcar (lambda (x) (message "argument: %s" x)) some-alist) to see what is the argument you get, and work from there. If that's an associative list, I'd imagine the output to be something like argument: (foo . bar), then yo could access foo using car and bar using cdr functions. – wvxvw Mar 19 '15 at 20:33
  • Yes, I did the same (just that I used the nth fn instead of car and cadr) but sequencep check in mapcar errored out. I was providing an alist as input but still mapcar didn't think that was a sequence. If I did (sequencep my-alist), that was non-nil. So I am confused.. I have to yet debug that. – Kaushal Modi Mar 19 '15 at 20:36
  • @kaushalmodi I would imagine two reasons: my-alist was nil or you forgot (or added extra) quotes so that my-alist was either a symbol, or was evaluated even further to be something else. You probably want to expand your question with the new code to make it easier to answer. – wvxvw Mar 19 '15 at 20:40
2
(dolist (fun '(foo bar baz))
  (defalias fun (lambda (a)
                  "I'm a function defined in `dolist'!"
                  (interactive)
                  (message a))))
(bar "See? No macro!")

Not exactly defuns but why not? :P

0

I have the following in my 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)

It is perhaps slightly more complex than needed (especially that extra eval) but it does allow me to generate the defuns I need for those properties (and include docstrings with the correct information in the strings).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.