Clojure:如何在运行时找出函数的参数?
给定一个函数对象或名称,我怎样才能确定它的arity? 像(arity func-name)
这样的东西。
我希望有一个办法,因为在Clojure中,arity非常重要
函数的arity存储在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))
build立在@ whocaresanyway的解决scheme:
(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))))))
其实它也适用于macros:
(defn arg-count [f] (let [m (first (.getDeclaredMethods (class f))) p (.getParameterTypes m)] (alength p))) (defmacro my-macro []) (arg-count @#'my-macro) ; 2
为什么2? 因为每个macros都有两个隐含的参数&form
和&env
。
在解决问题的同时,build立在其他解决scheme之上:
(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))))
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"