0

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 is-foo returns 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-bar around is-foo such that if is-bar returns 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 (void-function orig)

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 (f) and (apply f args) ought to evaluate to exactly the same thing.

So why don't they?

nitty-gritty details

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 flyspell-generic-check-word-predicate.

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

1

1 Answer 1

2

When I try to actually run the underlying code, what I get is an error about (void-function orig)

The error message says orig is not a function.

In your code, orig is a variable, not a function. In Emacs Lisp, a symbol can be a variable and a function at the same time without conflict, Which one will be used depends on their location in the function call (func arg1 arg2 ...), e.g.,

(defun foo () 1)
(defvar foo 2)

;; use the function foo
(foo)
;; => 1

;; use the variable foo
(identity foo)
;; => 2

Sometimes, the value of a variable is a function object, and you want to call the function, you use funcall or apply, e.g.,

(defun foo () 1)
(setq foo (lambda () 123))

;; use the function foo
(foo)
;; => 1

;; use the variable foo
(funcall foo)
;; => 123

funcall is simpler and apply is more generic, e.g.,

(+ 1 2 3)
;; => 6

;; there is only one way to use funcall
(funcall '+ 1 2 3)
;; => 6

;; there are many ways to use apply
(apply '+ '(1 2 3))
;; => 6

(apply '+ 1 '(2 3))
;; => 6

(apply '+ 1 2 '(3))
;; => 6

(apply '+ 1 2 3 ())
;; => 6

and when the function accepts zero arguments

(defun foo () 1)

(foo)

(funcall 'foo)
;; => 1

;; the function arguments is always a list, it's empty list for zero arguments function
(apply 'foo ())
;; => 1

Consider a predicate function is-foo that takes zero arguments

(apply orig args)

It's (apply orig ()) since args is always () (or nil: nil and () are eq hence the same thing), but (funcall orig) is easier to read.


There are more than one ways to write the advice function (that's do-better-advice in this case), e.g.,

(defun foo (a b) (+ a b))

(advice-add 'foo :around (lambda (orig a b)))
(advice-add 'foo :around (lambda (orig a &rest r)))
(advice-add 'foo :around (lambda (orig &rest r)))
(advice-add 'foo :around (lambda (orig &optional a b)))

it's because the advice function will be called with apply, in the following FUNCTION is do-better-advice, OLDFUN is is-foo

;; C-h f add-function
`:around'   (lambda (&rest r) (apply FUNCTION OLDFUN r))
4
  • Huh, thank you. And, wow, that's weird: I've never used a language before that has first class functions, but where the interpreter doesn't know that a function actually is a function when you pass it around as a variable unless you tell it. Interesting! Commented Dec 13, 2020 at 15:41
  • A variable is a symbol. A variable whose value is a function can be used in many other ways than just invoking the function - precisely because it is a symbol. As a symbol, it can have many other properties, besides just its symbol-value, that is, besides its value as a variable. It's not that the interpreter doesn't know that the symbol-value is a function. It's that you didn't know it - or rather, you didn't tell the interpreter to use the variable value as a function. In addition, don't confuse a symbol-value that's a function with symbol-function.
    – Drew
    Commented Dec 13, 2020 at 18:11
  • @Drew thanks for that---I find it a little confusing though. Aren't the semantics of lisps such that the first thing in a (unquoted) list is always called as a function? That's why it's confusing to me that (orig) wouldn't be treated as a function call. After googling symbol-function and ending up at gnu.org/software/emacs/manual/html_node/elisp/… a second question is: could I have called orig by ((symbol-function orig))? Commented Dec 14, 2020 at 2:56
  • Yes, (orig) says to invoke function orig. But orig is not a function. It's a local variable (a formal parameter/argument) for your function not-cite-advice. So no to your second question also: symbol-function is not for local variables.
    – Drew
    Commented Dec 14, 2020 at 16:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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