14

I'd like to temporarily override a function in a piece of code.

Take, for example, the following:

(defun nadvice/load-quiet (args)
  (cl-destructuring-bind
      (file &optional noerror nomessage nosuffix must-suffix)
      args
    (list file noerror t nosuffix must-suffix)))

(defun nadvice/idle-require-quiet (old-fun &rest args)
    (advice-add 'load :filter-args #'nadvice/load-quiet)
    (apply old-fun args)
    (advice-remove #'load #'nadvice/load-quiet))

(advice-add 'idle-require-load-next :around #'nadvice/idle-require-quiet)

What doesn't work:

  • This. It would be much cleaner if I could avoid manually enabling and disabling the advice and trust the single-threaded nature of Emacs to take care of things.
  • cl-letf won't let me reference the origional function, so I can't implement things that :filter-args would normally do.
  • cl-flet can't override functions in other functions.
  • noflet is an external package, which I'd like to avoid. (Also does much more than I need)

1 Answer 1

18

Couldn't you use (cl-)letf while referencing the original function yourself?

Something like this:

;; Original function
(defun my-fun (arg)
  (message "my-fun (%s)" arg))


;; Standard call
(my-fun "arg") ;; => my-fun (arg)


;; Temporary overriding (more or less like an around advice)
(let ((orig-fun (symbol-function 'my-fun)))
  (letf (((symbol-function 'my-fun)
          (lambda (arg)
            ;; filter arguments
            (funcall orig-fun (concat "modified-" arg)))))
    (my-fun "arg")))
;; => my-fun (modified-arg)


;; The overriding was only temporary
(my-fun "arg") ;; => my-fun (arg)



You can also wrap this in a macro if you plan to reuse it:

(defmacro with-advice (args &rest body)
  (declare (indent 1))
  (let ((fun-name (car args))
        (advice   (cadr args))
        (orig-sym (make-symbol "orig")))
    `(cl-letf* ((,orig-sym  (symbol-function ',fun-name))
                ((symbol-function ',fun-name)
                 (lambda (&rest args)
                   (apply ,advice ,orig-sym args))))
       ,@body)))

The example above can then be rewritten like the following:

(defun my-fun (arg)
  (message "my-fun (%s)" arg))


(my-fun "my-arg")

(with-advice (my-fun
              (lambda (orig-fun arg)
                (funcall orig-fun (concat "modified-" arg))))
  (my-fun "my-arg"))

(my-fun "my-arg")
3
  • Thanks, this is exactly what I was looking for. The only thing I'd change is use cl-letf* for both lets.
    – PythonNut
    Commented Sep 10, 2015 at 14:15
  • I added a macro version which uses only one letf* form for both bindings. Commented Sep 10, 2015 at 19:02
  • Nice! I might use it if I end up needing the advice in a lot of places.
    – PythonNut
    Commented Sep 10, 2015 at 19:04

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.