3

Say, an external package defines these functions:

(defun my/inner (x)
  (1+ x))

(defun my/outer ()
  (my/inner 7))

We want to create a function that wraps my/outer, replacing my/inner with the following:

(defun my/replacement (x)
  (1- x))

Using advice, we can do it like so:

(defun my/wrapped1 ()
  (add-function :override (symbol-function 'my/inner)
                'my/replacement)
  (unwind-protect
      (my/outer)
    (remove-function (symbol-function 'my/inner)
                     'my/replacement)))

What happens internally when using advice? I am unable to find good documentation on this topic.

The issue that interests me the most is the interaction between advice and byte compilation (Manual entry). The conclusion of This article from 12 years ago is that there is no way of using advising certain functions as they have their own bytecode instructions.

In particular, how is my/wrapped1 different from the following?

(defun my/wrapped2 ()
  (cl-letf (((symbol-function 'my/inner)
             'my/replacement))
    (my/outer)))

Does this address any of these issues or is advice just a fancy API around editing the symbol-function of a function definition and thus these two are equivalent?

PS: There is also defalias but its docstring explicitly says it internally uses fset (i.e., equivalent to cl-letf)

1
  • 1
    See Advising named functions in the Emacs Lisp Reference manual for caveats: "It is possible to advise a primitive (...), but one should typically not do so, [...]. Firstly, some primitives are used by the advice mechanism, and advising them could cause an infinite recursion. Secondly, many primitives are called directly from C, and such calls ignore advice; hence, one ends up in a confusing situation where some calls (occurring from Lisp code) obey the advice and other calls (from C code) do not. " Commented Jun 22 at 3:58

1 Answer 1

4

is advice just a fancy API around editing the symbol-function of a function definition and thus these two are equivalent?

Yes and mostly yes. The easiest thing is to inspect the results. I've used advice-add here for simplicity.

(symbol-function 'my/inner)
#[(x) ((1+ x)) (t)]

(symbol-function 'my/replacement)
#[(x) ((1- x)) (t)]

(cl-letf (((symbol-function 'my/inner) 'my/replacement))
  (symbol-function 'my/inner))
my/replacement

(cl-letf (((symbol-function 'my/inner) (symbol-function 'my/replacement)))
  (symbol-function 'my/inner))
#[(x) ((1- x)) (t)]

(advice-add 'my/inner :override #'my/replacement)
(symbol-function 'my/inner)
#[128 "\304\300\"\207" [my/replacement #[(x) ((1+ x)) (t)] :override nil apply] 4 advice]

(disassemble (symbol-function 'my/inner))
byte code:
  args: (x)
0   constant  apply
1   constant  my/replacement
2   stack-ref 2
3   call      2
4   return

(advice-remove 'my/inner #'my/replacement)
(symbol-function 'my/inner)
#[(x) ((1+ x)) (t)]

So you can see the internal differences between the two, but the effects are equivalent.

I would almost invariably use cl-letf in your scenario.

2
  • Thanks for the visualization! Would you also prefer cl-letf if the advice type were :after, for example? Commented Jun 22 at 13:10
  • 1
    I think it would just depend on the circumstances. And if using advice, I might well keep the advice active long-term (maybe based on the state of some minor mode) with the custom function acting conditionally upon a variable (to be let-bound around code instead of rebinding the function definition itself). Pick whatever makes best sense to you for what you're doing, though. Commented Jun 22 at 14:17

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.