Для объекта функции или имени, как я могу определить его арность? Что-то вроде (arity func-name)
.
Я надеюсь, что есть способ, так как arity довольно центральна в Clojure
Для объекта функции или имени, как я могу определить его арность? Что-то вроде (arity func-name)
.
Я надеюсь, что есть способ, так как arity довольно центральна в Clojure
Арность функции сохраняется в метаданных var.
(:arglists (meta #'str))
;([] [x] [x & ys])
Для этого требуется, чтобы функция была определена с помощью defn
или метаданных :arglists
, указанных явно.
Скрытое отражение:
(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))
Основываясь на решении @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))))))
На самом деле он также работает с макросами:
(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
соответственно.
Мое решение проблемы 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))))
Мое сердце кровоточит (охвачено все случаи).
(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)
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"