I'm new to elisp, and I'm struggling to figure out how to do advice. I got my code working, but I'm not sure why.
This is from a real problem that I had digging into markdown mode to add an extra exclusion to spellchecking to its implementation of
flyspell-generic-word-predicate---but the details don't matter, so I'll just reduce it to an abstract form to start with, and see if there's an easy explanation... but then I'll provide the full details later, in case my abstract theory of what's going on is wrong.
Consider a predicate function
is-foo that takes zero arguments and runs a test against the word at point.
Suppose I decide that the implementation of some library function
is-foo is deficient for my purposes, because there's a small, easily testable, class of words for which
t but I'd prefer it returns
nil. So I write a predicate function of my own, which also takes no arguments---call it
is-bar---which correctly handles the case that
is-foo screws up.
And then I decide that the best way to handle matters is to just use advice to wrap
is-foo such that if
nil, the whole function just returns
nil; otherwise, it returns whatever
is-foo would return.
So here's my first try:
(defun do-better-advice (orig) (if (is-bar) (orig) nil)) (advice-add 'is-foo :around #'do-better-advice)
When I try to actually run the underlying code, what I get is an error about
However, with the following small change, my code runs correctly:
(defun do-better-advice (orig &rest args) (if (is-bar) (apply orig args)))
What I don't understand is why calling
apply makes this work? The documentation for apply says that "apply calls function with arguments" and that "apply returns the result of calling function." But if that's true, then when
f takes no arguments, and is passed none (i.e.
args above is an empty list), then
(apply f args) ought to evaluate to exactly the same thing.
So why don't they?
In real life, this was an attempt to get flyspell to stop marking pandoc citation references (which begin with an ampersand) as spelling errors in markdown-mode. The source of the function I was advising,
markdown-flyspell-check-word-p is here; as you can see, it is a predicate that takes no arguments. Ultimately, the markdown-mode library just takes
markdown-flyspell-check-word-p and binds it to
My code that failed was:
(defun is-ampersand (s) (string= "@" s)) (defun not-cite () (save-excursion (forward-word -1) (let ((result (is-ampersand (string (preceding-char))))) (not result)))) (defun not-cite-advice (orig) (if (not-cite) (orig) nil)) (advice-add 'markdown-flyspell-check-word-p :around #'not-cite-advice)
And the specific error I got when I turned on flyspell was
Error in post-command-hook (flyspell-post-command-hook): (void-function orig)
But when I simply rewrote to use apply and to pass it a presumably empty list of args, it worked exactly as intended:
(defun not-cite-advice (orig &rest args) (if (not-cite) (apply orig args)))
The only reason I actually knew to try
apply was because I saw a couple examples of using
:around in advice, and they all used
apply. The documentation for around just says "Call function instead of the old function, but provide the old function as an extra argument to function" --- it doesn't say anything like "and also pass around a list of the arguments" or anything like that. Although the quoted bit below I guess means that you can give it a list of args (?).