4

Is there a terse idiomatic way of calling a function in the manner of funcall (dereferencing function symbols) when that function may have a variably shorter function signature?

Say for example: a hook assumes a signature of (VALUE PARENT), but for convenience it would be nice to also allow functions with the signature (VALUE) or (). Evaluation using funcall will throw an error if the signature is any shorter than the arguments of the funcall invocation.

Here's a demonstration of the behavior I'm looking for:

(require 'dash)

(defun my/foo (a)
  (* a a))

(defun my/bar (a b)
 (* a b))

(defun my/easy-going-funcall (fn &rest args)
  ""
  (let ((num-args (length (cadr (if (symbolp fn)
                                      (symbol-function fn)
                                   fn)))))
     (apply fn (-slice args 0 num-args))))

(my/easy-going-funcall #'my/foo 2 3 4)  ;; 4
(my/easy-going-funcall #'my/bar 2 3 4) ;; 6
(my/easy-going-funcall (lambda () 42) 2 3 4) ;; 42

What equivalent does elisp have for something like my/easy-going-funcall?

P.S. I realize this method of reflection doesn't work for built-in C functions and breaks down with argument keywords, but I don't know any better yet!

Edit:

This really boils down to syntactic sugar for users and developers of a library. From the user's perspective, if a hook expects functions with signature (ARG1 ARG2 ARG3) and their function ignores all three arguments they could write (lambda (&rest _unused) ...) for legibility. If the hook itself has a mechanism to reliably call functions with shorter signatures (as this question asks) then the user may omit &rest _unused entirely and write (lambda () ...) as a matter of convenience.

On the flip-side, developers have additional flexibility in defining hooks. Adding an additional argument to a hook signature does not need to be a breaking change.

Drew
  • 75,699
  • 9
  • 109
  • 225
ebpa
  • 7,319
  • 26
  • 53
  • As it happens, yes; I do happen to write a lot of JavaScript. Why do you ask? – ebpa Dec 12 '16 at 03:38
  • 1
    This looks like a terrible idea, somewhat inspired by currying and `apply-partially` or Scheme's `cut`. – wasamasa Dec 12 '16 at 08:10
  • 1
    How often does this case come up ? I think the idiomatic way is to wrap the function in a lambda which provides the required args. – YoungFrog Jan 07 '17 at 09:16
  • @YoungFrog The context for the question is hypothetical; not necessity. Consider as a package designer you provide a hook for users and as a matter of convenience users may add functions as I describe (shorter signatures than the hook would otherwise expect). Users may then attach functions more flexibly or tersely than otherwise (ex: `(lambda () (foo "bar"))` vs `(lambda (arg1 arg2 arg3 arg4) (foo "bar"))`). Users need not declare unused parameters and it offers the package designer some additional flexibility in extending the hook signature. I hope that answers your question. – ebpa Jan 07 '17 at 16:44
  • 1
    This is also important if you (as a package developer) need to add an additional argument to callback without breaking user's configurations. – dshepherd Dec 16 '17 at 08:59

2 Answers2

4

I generally recommend against doing that (better change the functions to take &optional or &rest arguments that are simply ignored), but if you must, the solution I recommend is:

(condition-case nil
    (funcall f arg1 arg2 arg3)
  (wrong-number-of-arguments
   (condition-case nil
       (funcall f arg1 arg2)
     (wrong-number-of-arguments
      ...))))

Of course, some people will object that this is broken: what if the wrong-number-of-arguments is signaled by some function call internal to f? Well, there's really not much you can do, because "internal" is in the eye of the beholder when you consider advice and other wrappers. E.g. consider what should happen in your example for a case such as:

(my/easy-going-funcall (lambda (&rest args)
                         (apply (lambda () 42) args))
                       2 3 4)
Stefan
  • 26,154
  • 3
  • 46
  • 84
1

I can't say I like the idea... but maybe I only need to get used to it. Anyway, I think you can come very close to your goal :

The function help-function-arglist tries hard to find the signature of any given function/subr ; this can be a starting point for rewriting your easy-going-funcall to handle more cases.

Another starting point is the source code of funcall, which you could change to suit your needs. It's defined in C, though, so it might not be possible to do it entirely in Lisp, but otoh you probably don't need the bits that are not doable from lisp.

YoungFrog
  • 3,496
  • 15
  • 27
  • For information these are thoughts and ideas that arose while chatting (conversation starts at http://chat.stackexchange.com/transcript/message/34617692#34617692). – YoungFrog Jan 07 '17 at 18:43