12

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.
Kaushal Modi
  • 25,203
  • 3
  • 74
  • 179
  • 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](https://github.com/kaushalmodi/.emacs.d/tree/98e2a92831f401a934d17cde6241dbfca636391f/setup-files/setup-visual.el#L30-L73). Currently the alist consists of only `cons`es 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

3 Answers3

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

Kaushal Modi
  • 25,203
  • 3
  • 74
  • 179
wvxvw
  • 11,222
  • 2
  • 30
  • 55
  • 4
    I think the last marginal note should be in all bold caps:) – abo-abo Mar 19 '15 at 07: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
  • @wvxvw OK the problem is not the alist. I tried implementing your solution and just replaced the last portion with `(setq my/defun-list '(zz-abc zz-def zz-ghi))` and `(my/create-defuns my/defun-list)` instead of hard-coding the macro argument. In my application, I plan to use `defconst` to declare the alist. And the error I get is the same one: `(wrong-type-argument sequencep my/defun-list)`. – Kaushal Modi Mar 20 '15 at 01:45
  • @wvxvw Looks like I've hit a road block: apparently macro arguments can't be evaluated. Will keep on looking on how to get the functionality I am looking for. There has to be a way. I want to create defuns named after each symbol in a list stored in a variable. – Kaushal Modi Mar 20 '15 at 02:33
  • @kaushalmodi I've added an example where the macro reads function names from a variable which points to an associative list, see if that helps. – wvxvw Mar 20 '15 at 07:28
  • Thanks for staying along with me through this debugging journey! That updated solution finally works for me :) – Kaushal Modi Mar 20 '15 at 13:52
  • Note to self: `(pp (macroexpand .. ))` is much easier on the eyes :) – Kaushal Modi Mar 20 '15 at 14:10
  • @abo-abo knows O.O – Dodgie Jan 11 '17 at 05:53
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

JAre
  • 175
  • 5
1

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

Jonathan Leech-Pepin
  • 4,307
  • 1
  • 19
  • 32