How can I determine number of arguments to a funct

2020-06-08 09:50发布

问题:

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 [] ...) has has zero arguments

回答1:

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.



回答2:

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


标签: clojure