12

What I mean by a "transparent 'pass-through' function wrapper" is a function, let's call it wrapper, that returns the result from passing all its argument to some other function, let's call it wrappee.

How is this done in Emacs Lisp?

NB: The ideal wrapper function is agnostic about the wrappee function's signature; i.e. it knows nothing of the number, positions, names, etc. of wrappee's arguments; it just passes all its arguments along to wrappee, just as if wrappee had been the one originally called. (There's no need, however, to mess with the call stack to replace the call to wrapper with a call to wrappee.)

I posted a partial answer to my question:

(defun wrapper (&rest args) (apply 'wrappee args))

This works only when wrappee is not interactive. Apparently, the way interactive functions get their arguments represents a different "channel" from what's covered by the (&rest args) incantation. What I still need, therefore, is an equally-wrappee-agnostic counterpart of the (&rest args) signature for the case where wrappee is an interactive function.

(This question was motivated by a problem described in this earlier question.)


In case further clarification of what I'm asking for is needed, below are a couple of examples, showing the Python and JavaScript equivalents of what I'm after.

In Python a couple of standard ways to implement such a wrapper are shown below:

def wrapper(*args, **kwargs):
    return wrappee(*args, **kwargs)

# or

wrapper = lambda *args, **kwargs: wrappee(*args, **kwargs)

(Here *args stands for "all the positional arguments", and **kwargs stands for "all the keyword arguments".)

The JavaScript equivalent would be something like this:

function wrapper () { return wrappee.apply(this, arguments); }

// or

wrapper = function () { return wrappee.apply(this, arguments); }

For the record, I disagree that this question is a duplicate of How to apply mapcar to a function with multiple arguments. I am at a loss to explain why, since the two questions look so obviously different to me. It is like being asked "explain why an apple should not be considered equivalent to an orange". The mere question is so crazy, that one doubts one could ever come up with an answer that would satisfy the person asking it.

kjo
  • 3,145
  • 14
  • 42
  • Did you consider using advice/nadvice? – wasamasa Jan 02 '16 at 17:46
  • @wasamasa: no, and, moreover, I fail to see how advice/nadvice would apply to this question. In any case, I find the `advice` stuff problematic enough that I'd rather stay clear of it. In fact, the motivation for this question was trying to find a solution to an otherwise intractable problem I have with an adviced function... – kjo Jan 02 '16 at 17:49
  • 1
    @wasamasa: Advice presents the same problem. You can tell it what to do with any of the args, but to make it interactive you need to specify how the arguments are to be provided. IOW, you need to provide an `interactive` spec. – Drew Jan 02 '16 at 18:07
  • 1
    What I did mean is advising the original interactive function to do whatever you want to happen before and after it, that way you shouldn't need to worry about the interactive spec. – wasamasa Jan 02 '16 at 18:16
  • 2
    @wasamasa: Yes, but that's different. Advice is always for a *particular* function, whether interactive or not. And if it is a command then there is no problem - its interactive behavior is inherited for the advised command (unless the advice redefines the interactive behavior). This question is about an *arbitrary* function/command, not a particular one. – Drew Jan 02 '16 at 19:28

3 Answers3

14

I had to solve a very similar problem in nadvice.el, so here is a solution (which uses some of the code from nadvice.el):

(defun wrapper (&rest args)
  (interactive (advice-eval-interactive-spec
                (cadr (interactive-form #'wrappee))))
  (apply #'wrappee args))

Compared to the other solutions posted so far, this one has the advantage of working correctly if wrappee gets redefined with a different interactive spec (i.e. it won't keep using the old spec).

Of course, if you want your wrapper to be truly transparent, you can do it more simply:

(defalias 'wrapper #'wrappee)
Stefan
  • 26,154
  • 3
  • 46
  • 84
  • This is the only answer that allows to define a wrapper that finds what it wraps at runtime. For example, I want to a add a shortcut that performs an action defined by some command that is looked up at runtime. Using `advice-eval-interactive-spec` as suggested here I can construct the interactive spec that correspond to that dynamic wrapper. – Igor Bukanov Aug 14 '16 at 18:06
  • Is it possible to make `called-interactively-p` return `t` in `wrappee`? There is `funcall-interactively` but no `apply-interactively` – clemera Aug 23 '17 at 14:17
  • 1
    @compunaut: Of course, you can do `(apply #'funcall-interactively #'wrappee args)` if you want. But you should only do it if the function is called interactively, so something like `(apply (if (called-interactively-p 'any) #'funcall-interactively #'funcall) #'wrappee args)`. – Stefan Aug 23 '17 at 20:34
  • Ha, thanks! Somehow could not think outside of my box. – clemera Aug 24 '17 at 06:02
13

Of course it is possible inclusive the interactive specification. We are dealing here with elisp! (Lisp is the language where the most important constructs are lists. Callable forms are just lists. So you can construct them after your liking.)

Application: You want to add some functionality to some functions automagically. The extended functions should be given new names so that defadvice is not applicable.

First a version that fits quite exactly your purpose. We set the function cell (fset) of the symbol wrapper with all required information from wrappee and add our extra stuff.

It works for both wrappee definitions. The first version of wrappee is interactive the second is not.

(defun wrappee (num str)
  "Nontrivial wrappee."
  (interactive "nNumber:\nsString:")
  (message "The number is %d.\nThe string is \"%s\"." num str))

(defun wrappee (num str)
  "Noninteractive wrappee."
  (message "The number is %d.\nThe string is \"%s\"." num str))

(fset 'wrapper (list 'lambda
             '(&rest args)
             (concat (documentation 'wrappee t) "\n Wrapper does something more.")
             (interactive-form 'wrappee)
             '(prog1 (apply 'wrappee args)
            (message "Wrapper does something more."))))

But it is more convenient to define a macro that constructs the extended functions. Therewith we can even specify the function names afterwards. (Good for an automated version.)

After executing the code below you can call wrapper-interactive interactively and wrapper-non-interactive non-interactively.

(defmacro make-wrapper (wrappee wrapper)
  "Create a WRAPPER (a symbol) for WRAPPEE (also a symbol)."
  (let ((arglist (make-symbol "arglist")))
  `(defun ,wrapper (&rest ,arglist)
     ,(concat (documentation wrappee) "\n But I do something more.")
     ,(interactive-form wrappee)
     (prog1 (apply (quote ,wrappee) ,arglist)
       (message "Wrapper %S does something more." (quote ,wrapper))))))

(defun wrappee-interactive (num str)
  "That is the doc string of wrappee-interactive."
  (interactive "nNumber:\nsString:")
  (message "The number is %d.\nThe string is \"%s\"." num str))

(defun wrappee-non-interactive (format &rest arglist)
  "That is the doc string of wrappee-non-interactive."
  (apply 'message format arglist))

(make-wrapper wrappee-interactive wrapper-interactive)
(make-wrapper wrappee-non-interactive wrapper-non-interactive)
;; test of the non-interactive part:
(wrapper-non-interactive "Number: %d, String: %s" 1 "test")

Note, up to now I did not yet find a way to transfer the declare forms but that should also be possible.

Tobias
  • 32,569
  • 1
  • 34
  • 75
  • 2
    Hm, someone down-voted this answer. I do not really care about the score but what I care is the reason for down-voting the answer. If you down-vote, please also leave a comment! This would give me a chance to improve the answer. – Tobias Jan 02 '16 at 23:37
  • I don't know for sure, but this is bound to make anyone reading the code of a package using it go WTF. In most cases the more sensible option is to deal with it and write a function doing the wrapping manually (be it with apply or by rewriting parts of the interactive spec. – wasamasa Jan 03 '16 at 10:04
  • 2
    @wasamasa I partially agree. Nevertheless, there are cases where automatic instrumentation is obligatory. An example is `edebug`. Furthermore, there are functions where the `interactive`-specification is considerably larger than the function body. In such cases the rewrite of the `interactive` specification can be quite tedious. The question and the answer address the required principles. – Tobias Jan 03 '16 at 10:24
  • 1
    Personally, I find this answer quite instructive, not only in regards of the question's scope, but also as it shows a natural application of macros, and how one steps from defun to macro. Thanks! – gsl Apr 07 '16 at 07:37
1

edit: Tobias' answer is nicer than this, as it obtains the precise interactive form and docstring of the wrapped function.


Combining the answers of Aaron Harris and kjo, you might use something like:

(defmacro my-make-wrapper (fn &optional name)
  "Return a wrapper function for FN defined as symbol NAME."
  `(defalias ',(or (eval name)
                   (intern (concat "my-" (symbol-name (eval fn)) "-wrapper")))
     (lambda (&rest args)
       ,(format "Generic wrapper for %s."
                (if (symbolp (eval fn))
                    (concat "`" (symbol-name (eval fn)) "'")
                  fn))
       (interactive)
       (if (called-interactively-p 'any)
           (call-interactively ,fn)
         (apply ,fn args)))))

Usage:

(my-make-wrapper 'find-file 'wrapper-func)

Call wrapper with either of:

(wrapper-func "~/.emacs.d/init.el")

M-x wrapper-func

phils
  • 48,657
  • 3
  • 76
  • 115