7

The advice feature allows modifying the behavior of a function globally. An advice definition can make calls to the original function.

(defadvice foo
  (around foo-bar activate compile)
  "Always set `qux' to t when running `foo'."
  (let ((qux t))
    ad-do-it))

The cl package provides the flet macro to override a function locally.

(defun foo ()
  "global")
(flet ((foo ()
          "local"))
  (some-code-that-calls-foo))

That doesn't permit a reference to the original foo function. What if the local override needs to call the original function?

(defun foo ()
  "global")
(flet ((foo ()
          (concat (foo) "+local")))
  ;; this will cause an infinite loop when (foo) is called
  (some-code-that-calls-foo))

This straightforward approach doesn't work, for good reason: (foo) is a recursive call to the local definition.

What's a non-cumbersome way of locally overriding a function, that allows calling the original function from the override code?

Application: monkey-patching some existing code, in a case where foo should not be rebound globally, but the code needs to call the original. Here's the latest example I've been wanting:

(defadvice TeX-master-file
  (around TeX-master-file-indirect-buffer activate compile)
  "Support indirect buffers."
  (flet ((buffer-file-name (&optional buffer)
           (<global buffer-file-name> (buffer-base-buffer buffer))))
      ad-do-it)))

I wanted to rebind buffer-file-name locally, and call the original buffer-file-name. Ok, in this specific case, there's a workaround, which is to use the buffer-file-name variable. But the point of my question here is the general technique. How can I bind a function (here buffer-file-name) locally but call the global definition from my redefinition?

This is for my .emacs, which I keep working in Emacs 19.34, so solutions that require Emacs 24.4 are out. I do prefer solutions that cope with lexical binding cleanly though — but monkey-patching is inherently about dynamic binding.

6

Store the original function (obtained with symbol-function) in a local variable, and use funcall to call the function object stored in that variable. Cumbersome, but it mostly works.

(defadvice TeX-master-file
  (around TeX-master-file-indirect-buffer activate compile)
  "Support indirect buffers."
  (let ((original-buffer-file-name (symbol-function 'buffer-file-name)))
    (flet ((buffer-file-name (&optional buffer)
             (funcall original-buffer-file-name (buffer-base-buffer buffer))))
        ad-do-it)))

This mostly works, in that it does what it's supposed to do, but it can break in rare circumstances. Since Emacs Lisp has no primitive way to define symbols' function slots locally (only variable slots, with let), flet sets the bindings and restores them through unwind-protect. If the code dies because the call nesting exceeded max-lisp-eval-depth, or if the binding is modified during the execution of this function (e.g. because you're debugging the advice), it's possible for the symbol's function slot not to be restored. You may want to take precautions against accidentally losing some functions.

Another method is to store a copy of the function. This has the advantage that the original function is never lost.

(fset 'original-buffer-file-name (symbol-function 'buffer-file-name))
(defadvice TeX-master-file
  (around TeX-master-file-indirect-buffer activate compile)
  "Support indirect buffers."
  (flet ((buffer-file-name (&optional buffer)
           (original-buffer-file-name (buffer-base-buffer buffer))))
      ad-do-it)))

This would be fine in this specific case, because buffer-file-name is a built-in function that's unlikely to be rebound, but would not track redefinitions of the global function (for example to add advice to that function).

  • Do you have an nadvice recipe for the same? – Kaushal Modi Sep 21 '15 at 18:18
  • 1
    @kaushalmodi No. I only use newer features if they have a definite benefit. I still use 24.3 on a daily basis, so I stick to the advice mechanism that's worked since Emacs 19 (or earlier?). – Gilles Sep 21 '15 at 18:19

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.