functional programming - Clojure: ¿Cómo averiguar la funcionalidad de la función en tiempo de ejecución?
functional-programming (7)
Basándose en la solución de @ 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))))))
Dado un objeto o nombre de función, ¿cómo puedo determinar su aridad? Algo así como (arity func-name)
.
Espero que haya una manera, ya que arity es bastante central en Clojure
En realidad, también funciona en macros:
(defn arg-count [f]
(let [m (first (.getDeclaredMethods (class f)))
p (.getParameterTypes m)]
(alength p)))
(defmacro my-macro [])
(arg-count @#''my-macro)
; 2
¿Por qué 2? Porque cada macro tiene dos argumentos implícitos &form
y &env
respectivamente.
La ariadidad de una función se almacena en los metadatos de la var.
(:arglists (meta #''str))
;([] [x] [x & ys])
Esto requiere que la función se defina usando defn
, o los metadatos :arglists
suministrados explícitamente.
Mi corazón sangraba (cubría todos los casos).
(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)
Mi opinión sobre el problema arity, basándose en las otras soluciones:
(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))))
Reflexión furtiva:
(defn arg-count [f]
(let [m (first (.getDeclaredMethods (class f)))
p (.getParameterTypes m)]
(alength p)))
O bien:
(defn arg-count [f]
{:pre [(instance? clojure.lang.AFunction f)]}
(-> f class .getDeclaredMethods first .getParameterTypes alength))
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"