Как определить количество аргументов функции в Clojure?

Учитывая функцию x в clojure, как я могу программно получить счетчик количества аргументов?

например:

(fn a [b c] ...) has has two arguments
(fn a [] ...) has has zero arguments

Ответы

Ответ 1

Если у вас есть доступ к var, который содержит эту функцию, вы можете получить счетчик аргументов, обратившись к его метаданным следующим образом:

(defn arities [v]
  (->> v meta :arglists (map count)))

(defn a [])
(defn b [_ _])

(map arities [#'a #'b])
;= ((0) (2))

arities вернет seq со всеми arities для функции. Это имеет тот недостаток, что для вектора вариационных аргументов ([_ _ & _]) он вернется (4).

(defn c [_ _ & _])
(arities #'c)
;= (4)

Это можно устранить, удалив символ & из всех списков аргументов.

(defn arities [v]
  (->> v 
    meta 
    :arglists 
    (map #(remove #{'&} %))
    (map count)))

(arities #'c)
;= (3)

Если у вас нет доступа к var, следующая функция - это небольшая функция, которую я использовал для определения количества аргументов функции. Он использует отражение, поэтому не тот подход, который вы можете предпринять, если вам нужна хорошая производительность. Также учтите, что он опирается на детали реализации.

(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)

ИЗМЕНИТЬ

После того, как вы ответили другим, я понял, что результат 3 для вариационной функции, определяемой как (defn x [_ _ & _] ,,,), на самом деле довольно вводит в заблуждение, так как тот же результат вы получите для функции с тремя аргументами. Следующая версия вернет :variadic вместо определенного числа для векторов аргументов, содержащих символ & (за исключением случая [&], где & это имя фактического аргумента). Как упоминалось в комментарии Джереми Хейлера, подсчет аргументов из метаданных работает только в том случае, если значение для :arglists не изменено вручную.

(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))

Вариант отражения для этого немного сложнее, и он опирается на более подробные сведения о реализации (т.е. "Объявлена ​​ли та или иная функция?" или "Разве функция расширяет класс X?" ), поэтому я бы не рекомендовал используя этот подход.

Ответ 2

Вы можете построить функцию, берущую все аргументы, помещая их в список и возвращая счет так:

(defn arg-count [& rest] (count rest))
(arg-count) ;; 0
(arg-count 1 2 3) ;; 3