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)
Drew
  • 75,699
  • 9
  • 109
  • 225
PythonNut
  • 10,243
  • 2
  • 29
  • 75

1 Answers1

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")
François Févotte
  • 5,917
  • 1
  • 24
  • 37