Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Using variable names in a macro

3 views
Skip to first unread message

Narendra Joshi

unread,
Dec 9, 2017, 10:49:56 AM12/9/17
to help-gnu-emacs
Hi,

I want to refactor the following macro. I use it to do things at times
when I am idle for some time and when I come back to the keyboard
eventually. For example, showing bitcoin prices when I have been idle
for some time but removing them from the mode line once I am back as it
takes a lot of space. I think there is a lot of repetition but I am not
sure how to abstract things out into variables. It would have been
simpler if an anonymous function could refer itself.

```
(defmacro do-when-idle (f g interval)
"Call F when idle for INTERVAL seconds and then G when there is activity."
`(progn
(defun ,(intern (concat "do-when-idle-"
(sha1 (format "%s-%s" f g)))) ()
(funcall ,g)
(remove-hook 'post-command-hook
#',(intern (concat "do-when-idle-"
(sha1 (format "%s-%s" f g)))))
(run-with-idle-timer ,interval
nil
(lambda ()
(funcall ,f)
(add-hook 'post-command-hook
#',(intern (concat "do-when-idle-"
(sha1 (format "%s-%s" f g))))))))
(run-with-idle-timer ,interval
nil
(lambda ()
(funcall ,f)
(add-hook 'post-command-hook
#',(intern (concat "do-when-idle-"
(sha1 (format "%s-%s" f g)))))))))
```

Best,
--
Narendra Joshi

Michael Heerdegen

unread,
Dec 10, 2017, 6:28:52 AM12/10/17
to Narendra Joshi, help-gnu-emacs
Narendra Joshi <naren...@gmail.com> writes:

> ```
> (defmacro do-when-idle (f g interval)
> "Call F when idle for INTERVAL seconds and then G when there is activity."
> `(progn
> (defun ,(intern (concat "do-when-idle-"
> (sha1 (format "%s-%s" f g)))) ()
> (funcall ,g)
> (remove-hook 'post-command-hook
> #',(intern (concat "do-when-idle-"
> (sha1 (format "%s-%s" f g)))))
> (run-with-idle-timer ,interval
> nil
> (lambda ()
> (funcall ,f)
> (add-hook 'post-command-hook
> #',(intern (concat "do-when-idle-"
> (sha1 (format "%s-%s" f g))))))))
> (run-with-idle-timer ,interval
> nil
> (lambda ()
> (funcall ,f)
> (add-hook 'post-command-hook
> #',(intern (concat "do-when-idle-"
> (sha1 (format "%s-%s" f g)))))))))
> ```

Hmm, with some trivial simplifications of the list building, this could
look like:

#+begin_src emacs-lisp
(defmacro do-when-idle (f g interval)
"Call F when idle for INTERVAL seconds and then G when there is activity."
(let* ((name (intern (concat "do-when-idle-"
(sha1 (format "%s-%s" f g)))))
(run-idle-timer-form `(run-with-idle-timer
,interval
nil
(lambda ()
(funcall ,f)
(add-hook 'post-command-hook #',name)))))
`(progn
(defun ,name ()
(funcall ,g)
(remove-hook 'post-command-hook #',name)
,run-idle-timer-form)
,run-idle-timer-form)))
#+end_src

It would be better, though, to replace the run-idle-timer-form from with
a lambda, to avoid the code duplication.

Actually, `do-when-idle' doesn't even have to be a macro. You can make
it a defun when you define the NAME with defalias. Bind it to a closure
so that you can refer to all of the stuff with variables bound around
it.


Michael.

Narendra Joshi

unread,
Dec 10, 2017, 9:42:36 AM12/10/17
to Michael Heerdegen, help-gnu-emacs
This is a lot cleaner. Thanks! :)

> It would be better, though, to replace the run-idle-timer-form from with
> a lambda, to avoid the code duplication.
>
> Actually, `do-when-idle' doesn't even have to be a macro. You can make
> it a defun when you define the NAME with defalias. Bind it to a closure
> so that you can refer to all of the stuff with variables bound around
> it.
Can you share an example please? :)

--
Narendra Joshi

Michael Heerdegen

unread,
Dec 10, 2017, 12:33:03 PM12/10/17
to Narendra Joshi, help-gnu-emacs
Narendra Joshi <naren...@gmail.com> writes:

> Can you share an example please? :)

If you want to go with anonymous functions, `letrec' is perfect here
(`cl-labels' would work as well and avoid the need to `funcall'):

#+begin_src emacs-lisp
;; -*- lexical-binding: t -*-

(defun my-do-when-idle (f g interval)
"Call F when idle for INTERVAL seconds and then G when there is activity."
(letrec ((run-timer-fun (lambda () (run-with-idle-timer
interval nil
(lambda ()
(funcall f)
(add-hook 'post-command-hook activity-fun)))))
(activity-fun (lambda ()
(remove-hook 'post-command-hook activity-fun)
(funcall g)
(funcall run-timer-fun))))
(funcall run-timer-fun)))
#+end_src

I've changed the order in the `activity-fun' to run `remove-hook' first,
so that you get a sane behavior when running G gives an error.

A short test:

#+begin_src emacs-lisp
(my-do-when-idle (lambda () (message "%s" (current-time-string)))
(lambda () (message "Stopping timer"))
5)
#+end_src


Michael.


Narendra Joshi

unread,
Dec 11, 2017, 9:07:42 AM12/11/17
to Michael Heerdegen, help-gnu-emacs
Michael Heerdegen <michael_...@web.de> writes:

> Narendra Joshi <naren...@gmail.com> writes:
>
>> Can you share an example please? :)
>
> If you want to go with anonymous functions, `letrec' is perfect here
> (`cl-labels' would work as well and avoid the need to `funcall'):
>
> #+begin_src emacs-lisp
> ;; -*- lexical-binding: t -*-
>
> (defun my-do-when-idle (f g interval)
> "Call F when idle for INTERVAL seconds and then G when there is activity."
> (letrec ((run-timer-fun (lambda () (run-with-idle-timer
> interval nil
> (lambda ()
> (funcall f)
> (add-hook 'post-command-hook activity-fun)))))
> (activity-fun (lambda ()
> (remove-hook 'post-command-hook activity-fun)
> (funcall g)
> (funcall run-timer-fun))))
> (funcall run-timer-fun)))
> #+end_src
Perfect. `letrec` makes it very simple! :) Thanks a lot! :)
>
> I've changed the order in the `activity-fun' to run `remove-hook' first,
> so that you get a sane behavior when running G gives an error.
Makes sense! :)

Best,
--
Narendra Joshi

0 new messages