9

I've been doing some Genetic Programming and I've been separating functions into different function sets based on their arity; it's all rather complex.

I'd like to know if there's a simpler way to to do it. For example, if there's a function that returns the arity of a given function.

Cheers in advance.

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
Johnny McKenzie
  • 197
  • 2
  • 9
  • 2
    Seems to be implementation-dependent. See https://groups.google.com/forum/#!msg/comp.lang.lisp/0WoivuykcKM/0SnbqcFyNogJ – Frédéric Hamidi Mar 17 '13 at 19:29
  • There's a very close portable library: https://github.com/Shinmera/trivial-arguments but it returns the full lambda list, not the arity. – Ehvince Mar 25 '19 at 00:17

2 Answers2

15

For interpreted functions you should be able to use function-lambda-expression.

For compiled functions, alas, this function often returns nil, so you will have to use an implementation-dependent function (clocc/port/sys.lisp):

(defun arglist (fn)
  "Return the signature of the function."
  #+allegro (excl:arglist fn)
  #+clisp (sys::arglist fn)
  #+(or cmu scl)
  (let ((f (coerce fn 'function)))
    (typecase f
      (STANDARD-GENERIC-FUNCTION (pcl:generic-function-lambda-list f))
      (EVAL:INTERPRETED-FUNCTION (eval:interpreted-function-arglist f))
      (FUNCTION (values (read-from-string (kernel:%function-arglist f))))))
  #+cormanlisp (ccl:function-lambda-list
                (typecase fn (symbol (fdefinition fn)) (t fn)))
  #+gcl (let ((fn (etypecase fn
                    (symbol fn)
                    (function (si:compiled-function-name fn)))))
          (get fn 'si:debug))
  #+lispworks (lw:function-lambda-list fn)
  #+lucid (lcl:arglist fn)
  #+sbcl (sb-introspect:function-lambda-list fn)
  #-(or allegro clisp cmu cormanlisp gcl lispworks lucid sbcl scl)
  (error 'not-implemented :proc (list 'arglist fn)))

EDIT: note that arity in CL is not really a number, since Lisp functions can accept optional, rest and keyword arguments in addition to required ones; this is why the above arglist function returns the lambda list of the argument function, not a number.

If you are only interested in functions which accept only required parameters, you would need to use something like

(defun arity (fn)
  (let ((arglist (arglist fn)))
    (if (intersection arglist lambda-list-keywords)
        (error "~S lambda list ~S contains keywords" fn arglist)
        (length arglist))))
sds
  • 58,617
  • 29
  • 161
  • 278
  • Thanks, I just discovered that I can call clisp's own #'arglist, then do a member search on the list it returns. – Johnny McKenzie Mar 17 '13 at 21:11
  • 1
    This is a great answer, but also check the answer below "trivial-arguments"! Super light library, you can simply call (arglist fn) easily and portably. – Alberto Apr 12 '20 at 15:43
  • For [CCL](https://ccl.clozure.com) there is `ccl:arglist`, which should be added to the portable `arglist ` definition above (and ideally also at CLOCC). – Torsten Anders Aug 21 '20 at 11:38
1

There is a portable library that gives the function's lambda list: https://github.com/Shinmera/trivial-arguments

(ql:quickload "trivial-arguments")

Example:

(arg:arglist #'gethash)
;; => (sb-impl::key hash-table &optional sb-impl::default)

(defun foo (a b c &optional d) nil)
(arglist #'foo)  ;; => (a b c &optional d)

It returns the full lambda list, with &optional and stuff, so we can't just get the length of the result for the arity.

Ehvince
  • 17,274
  • 7
  • 58
  • 79
  • Great library! Have been using it a lot for some portable meta-programming! Couldn't be easier to choose. – Alberto Apr 12 '20 at 15:41