2

The following snippet, featuring the "fantasy function" clone-function, illustrates what I'd like to do:

(defvar last-enabled-foo nil)

;; hold on to a "clone"/copy of third-party function enable-foo
(defvar original-enable-foo (clone-function 'enable-foo))

;; replace enable-foo with a wrapper function around it
;; module foo.el
(defun enable-foo (foo)
  (setq last-enabled-foo foo)
  (funcall original-enable-foo foo))

Basically, clone-function ensures that original-enable-foo refers to a function that is completely distinct from the newly defined enable-foo.

How could one implement clone-function?

Drew
  • 77,472
  • 10
  • 114
  • 243
kjo
  • 3,247
  • 17
  • 48
  • 3
    Are you familiar with emacs function advice and not using it for a specific purpose? If you aren't aware, advice is how this sort of thing should be handled. https://www.gnu.org/software/emacs/manual/html_node/elisp/Advising-Functions.html – Jordon Biondo Jan 27 '16 at 21:19

2 Answers2

7

For variety, here's a solution using the :around advice.

Copy the below test snippet to the *scratch* buffer and evaluate the progn form.

(progn
  (defvar last-enabled-foo nil)
  (setq last-enabled-foo nil)

  (defun enable-foo (foo)
    (message "last-enabled-foo = %S" last-enabled-foo))

  (defun adv/enable-foo (orig-fun &rest args)
    (setq last-enabled-foo args)
    (apply orig-fun args))
  (advice-add 'enable-foo :around #'adv/enable-foo)
  ;; Comment the below line to see the advice in effect
  (advice-remove 'enable-foo #'adv/enable-foo)

  (enable-foo 9))

With the way it is right now, you will always see the output as:

last-enabled-foo = nil

This is because we are setting the last-enabled-foo value to nil and the value 9 passed to enable-foo is not being assigned to last-enabled-foo.

(setq last-enabled-foo nil)
(defun enable-foo (foo)
  (message "last-enabled-foo = %S" last-enabled-foo))
(enable-foo 9)

The advice code is ineffective as we are adding an advice and then again removing the same.


Now comment out the (advice-remove ..) line. Now the advice will be in effect and you will see the magic when you re-evaluate that same progn form.

Now, the output will be:

last-enabled-foo = (9)

Now, even though the setq form is setting the last-enabled-foo value to nil during each progn evaluation, it is being set to the enable-foo argument args inside the advice function adv/enable-foo.

(defun adv/enable-foo (orig-fun &rest args)
  (setq last-enabled-foo args)
  (apply orig-fun args))

References:

Kaushal Modi
  • 25,651
  • 4
  • 80
  • 183
4

I agree that using advice is probably the way to go, but if you really, truly have to, you can use something like this. (Comments are welcome.)

(defun third-party-func ()
  (message "original"))

(defalias 'third-party-func-original
          (symbol-function 'third-party-func))

(defun third-party-func ()
  (message "new!")
  (third-party-func-original))

; now (third-party-func) calls the patched version;
; (third-party-func-original) calls the original version
Constantine
  • 9,122
  • 1
  • 35
  • 50
  • I'd use defalias instead of fset. – wasamasa Jan 27 '16 at 21:51
  • 1
    @wasamasa: Do you mind explaining why? (I just learned about defalias-fset-function and that defalias records which file defined the function (like defun, but I have no idea what the implications are.) – Constantine Jan 27 '16 at 21:55
  • 1
    It is all about communicating intent. The purpose of this example is having an alias of the original function around, so you'd use defalias instead of fset. If you were doing something like building your own flet, fset would be the more idiomatic choice. – wasamasa Jan 27 '16 at 22:13
  • @wasamasa: Yup. Good point. – Constantine Jan 27 '16 at 22:20
  • 1
    symbol-function isn't needed either as both fset and defalias operate on the function slot. – wasamasa Jan 30 '16 at 13:11
  • @wasamasa: I believe symbol-function is needed. In my Emacs (24.5.1) (defalias 'third-party-func-original third-party-func) (no quoting) fails with Lisp error: (void-variable third-party-func) and (defalias 'third-party-func-original 'third-party-func) (quoted) creates an alias using the symbol third-party-func as the definition, which leads to infinite recursion in the new third-party-func and a failure with Lisp error: (error "Lisp nesting exceedsmax-lisp-eval-depth'")`. – Constantine Feb 01 '16 at 16:31
  • Meh, looks like a perfect usecase for noflet then. – wasamasa Feb 01 '16 at 19:48