Given a function x in clojure how can I programatically get a count of the number of arguments?
eg:
(fn a [b c] ...) has has two arguments
(fn a [] ...)
If you have access to the var that holds the function you can get the argument count by accessing its metadata in the following way:
(defn arities [v]
(->> v meta :arglists (map count)))
(defn a [])
(defn b [_ _])
(map arities [#'a #'b])
;= ((0) (2))
arities
will return a seq with all the arities for the function. This has the disadvantage that for a variadic argument vector ([_ _ & _]
) it will return (4).
(defn c [_ _ & _])
(arities #'c)
;= (4)
This could be fixed by removing the &
symbol from all the argument lists.
(defn arities [v]
(->> v
meta
:arglists
(map #(remove #{'&} %))
(map count)))
(arities #'c)
;= (3)
If you don't have access to the var, the following is a little function that I've used to detect the argument count of a function. It uses reflection so it's not the approach you might want to take if you need good performance. Also take into account that it relies on implementation details.
(defn n-args [f]
(-> f class .getDeclaredMethods first .getParameterTypes alength))
(defn a [])
(defn b [_ _])
(defn c [_ _ & _])
(map n-args [a b c])
;= (0 2 3)
EDIT
After giving the answer another read, I realized the result 3 for a variadic function defined as (defn x [_ _ & _] ,,,)
, is actually quite misleading since it's the same result you would get for a function with 3 arguments. The following version will return :variadic
, instead of a specific number, for the argument vectors that contain the &
symbol (except for the case [&]
where &
it's the actual argument name). As mentioned in a comment by Jeremy Heiler getting the argument count from the metadata only works if the value for :arglists
is not manually changed.
(defn a [_])
(defn b [_ _])
(defn c [_ _ & _])
(defn d [_ _ _])
(defn e [&])
(defn variadic? [s]
(and (some #{'&} s)
(not (every? #{'&} s))))
(defn arities [v]
(->> v
meta
:arglists
(map #(if (variadic? %) :variadic %))
(map #(if (sequential? %) (count %) %))))
(map arities [#'a #'b #'c #'d #'e])
;= ((1) (2) (:variadic) (3) (:variadic))
The reflection version for this is a little more complicated and it relies on more implementation details (i.e. "Is this or that function declared?" or "Does the function extend the class X?"), so I wouldn't recommend using that approach.
You could construct a function taking all arguments, putting them in a list and returning the count like so:
(defn arg-count [& rest] (count rest))
(arg-count) ;; 0
(arg-count 1 2 3) ;; 3