Clojure: Как узнать арность функции во время выполнения?

Для объекта функции или имени, как я могу определить его арность? Что-то вроде (arity func-name).

Я надеюсь, что есть способ, так как arity довольно центральна в Clojure

Ответ 1

Арность функции сохраняется в метаданных var.

(:arglists (meta #'str))
;([] [x] [x & ys])

Для этого требуется, чтобы функция была определена с помощью defn или метаданных :arglists, указанных явно.

Ответ 2

Скрытое отражение:

(defn arg-count [f]
  (let [m (first (.getDeclaredMethods (class f)))
        p (.getParameterTypes m)]
    (alength p)))

Или:

(defn arg-count [f]
  {:pre [(instance? clojure.lang.AFunction f)]}
  (-> f class .getDeclaredMethods first .getParameterTypes alength))

Ответ 3

Основываясь на решении @whocaresanyway:

(defn provided
  [cond fun x]
  (if cond
    (fun x)
    x))

(defn append
  [xs x]
  (conj (vec xs) x))

(defn arity-of-method
  [method]
  (->> method .getParameterTypes alength))

(defn arities
  [fun]
  (let [all-declared-methods (.getDeclaredMethods (class fun))
        methods-named (fn [name]
                        (filter #(= (.getName %) name) all-declared-methods))
        methods-named-invoke (methods-named "invoke")
        methods-named-do-invoke (methods-named "doInvoke")
        is-rest-fn (seq methods-named-do-invoke)]
    (->> methods-named-invoke
         (map arity-of-method)
         sort
         (provided is-rest-fn
                   (fn [v] (append v :rest))))))

Ответ 4

На самом деле он также работает с макросами:

(defn arg-count [f]
  (let [m (first (.getDeclaredMethods (class f)))
        p (.getParameterTypes m)]
    (alength p)))

(defmacro my-macro [])

(arg-count @#'my-macro)
; 2

Почему 2? Поскольку каждый макрос имеет два неявных аргумента &form и &env соответственно.

Ответ 5

Мое решение проблемы arity, основываясь на других решениях:

(defn arity
 "Returns the maximum parameter count of each invoke method found by refletion
  on the input instance. The returned value can be then interpreted as the arity
  of the input function. The count does NOT detect variadic functions."
  [f]
  (let [invokes (filter #(= "invoke" (.getName %1)) (.getDeclaredMethods (class f)))]
  (apply max (map #(alength (.getParameterTypes %1)) invokes))))

Ответ 6

Мое сердце кровоточит (охвачено все случаи).

(defn arity
  "Returns the maximum arity of:
    - anonymous functions like `#()` and `(fn [])`.
    - defined functions like `map` or `+`.
    - macros, by passing a var like `#'->`.

  Returns `:variadic` if the function/macro is variadic."
  [f]
  (let [func (if (var? f) @f f)
        methods (->> func class .getDeclaredMethods
                     (map #(vector (.getName %)
                                   (count (.getParameterTypes %)))))
        var-args? (some #(-> % first #{"getRequiredArity"})
                        methods)]
    (if var-args?
      :variadic
      (let [max-arity (->> methods
                           (filter (comp #{"invoke"} first))
                           (sort-by second)
                           last
                           second)]
        (if (and (var? f) (-> f meta :macro))
          (- max-arity 2) ;; substract implicit &form and &env arguments
          max-arity)))))

(use 'clojure.test)

(defmacro m ([a]) ([a b]))
(defmacro mx [])

(deftest test-arity
  (testing "with an anonymous #(… %1) function"
    (is (= 1           (arity #(+ % 32))))
    (is (= 1           (arity #(+ %1 32))))
    (is (= 2           (arity #(+ %1 %2))))
    (is (= 13          (arity #(+ %1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11 %12 %13))))
    (is (= :variadic   (arity #(apply + %&))))
    (is (= :variadic   (arity #(apply + % %&)))))
  (testing "with an anonymous (fn [] …) function"
    (testing "single body"
      (is (= 0         (arity (fn []))))
      (is (= 1         (arity (fn [a]))))
      (is (= 2         (arity (fn [a b]))))
      (is (= 20        (arity (fn [a b c d e f g h i j k l m n o p q r s t]))))
      (is (= :variadic (arity (fn [a b & more])))))
    (testing "multiple bodies"
      (is (= 0         (arity (fn ([])))))
      (is (= 1         (arity (fn ([a])))))
      (is (= 2         (arity (fn ([a]) ([a b])))))
      (is (= :variadic (arity (fn ([a]) ([a b & c])))))))
  (testing "with a defined function"
    (is (= :variadic   (arity map)))
    (is (= :variadic   (arity +)))
    (is (= 1           (arity inc))))
  (testing "with a var to a macro"
    (is (= :variadic   (arity #'->)))
    (is (= 2           (arity #'m)))
    (is (= 0           (arity #'mx)))))

(run-tests)

Ответ 7

user=> (defn test-func
         ([p1] "Arity was 1.")
         ([p1 p2] "Arity was 2.")
         ([p1 p2 & more-args] (str "Arity was " (+ 2 (count more-args)))))
#'user/test-func
user=> (test-func 1)
"Arity was 1."
user=> (test-func 1 2)
"Arity was 2."
user=> (test-func 1 2 3)
"Arity was 3"
user=> (test-func 1 2 3 4)
"Arity was 4"
user=> (test-func 1 2 3 4 5) ;...
"Arity was 5"