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
  • 75,699
  • 9
  • 109
  • 225
kjo
  • 3,145
  • 14
  • 42
  • 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,203
  • 3
  • 74
  • 179
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,072
  • 1
  • 34
  • 49
  • 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](https://github.com/nicferrier/emacs-noflet), `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 exceeds `max-lisp-eval-depth'")`. – Constantine Feb 01 '16 at 16:31
  • Meh, looks like a perfect usecase for [noflet](https://github.com/nicferrier/emacs-noflet) then. – wasamasa Feb 01 '16 at 19:48