I've been trying to a macro to put one-time advice on a function.

What I mean by one-time advice is advice which removes itself after it is called – so that when you add the advice and run the original function, the advice runs, but if you run the function again, the advice is gone.

My current (non-working) definition is

(defmacro add-one-time-advice (symbol where function &optional props)
  `(let ((ad-sym (make-symbol "advice")))
    (fset ad-sym (symbol-function ,function))
    (advice-add ad-sym :after (lambda () (advice-remove ,symbol ad-sym)))
    (advice-add ,symbol ,where ad-sym ,props)))

This throws no errors, but the advice doesn't remove itself.

If my approach is okay, how can I fix it, and if it's not, what's the cleanest way to implement this?


You don't need a macro for this. And you don't need (but you can certainly use) lexical binding.

;; Without lexical binding:
(defun advise-once (symbol where function &optional props)
  (advice-add symbol :after `(lambda (&rest _) (advice-remove ',symbol ',function)))
  (advice-add symbol where function props))

;; With lexical binding:
(defun advise-once (symbol where function &optional props)
  (advice-add symbol :after (lambda (&rest _) (advice-remove symbol function)))
  (advice-add symbol where function props))

(advise-once 'backward-word :before (lambda (_) (message "Eureka!")))

The reason you do not need lexical binding is that you are interested, when the function (lambda form) is used, only in the values of symbol and function, such as they were at the time the lambda-form list was created. You do not need variables symbol and function when the lambda form is used.

The use of the backquote syntax constructs a list that is a lambda form, where the values of symbol and function have been substituted in place of those variables.

Using lexical binding has the advantage that the lambda form can be compiled, because it is recognized by the compiler as a function, whereas in the case of using the backquote syntax all the compiler sees is code that constructs a list.

| improve this answer | |
  • I'm getting too macro-happy! This teaches me to try using a function first... I think the insight here is to use another advice to remove the first advice from the function (instead of advising the advice). Anyway, cheers. – digitalis_ Aug 14 '16 at 10:33

The code looks OK, actually (I would get rid of the call to symbol-function, tho).

My crystal ball tells me that maybe you get bitten by dynamic scoping: your code relies on lexical-scoping for its use of ad-sym from within the lambda, so it's indispensable that lexical-binding be non-nil in the code that uses this macro.

| improve this answer | |

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.