2009-11-08 19 views

Respuesta

48

La aridad de una función se almacena en los metadatos de la var.

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

Esto requiere que la función era o bien define utilizando defn, o los :arglists metadatos suministrados de forma explícita.

+0

Excelente. Muchas gracias – GabiMe

+2

Tenga en cuenta que esto realmente solo funciona para las funciones definidas con defn. No funciona para funciones anónimas definidas con fn o #(). – kotarak

+0

Por lo que puedo ver, funciona para todas las funciones integradas también. Por ejemplo (: arglists (meta # '+)) o (: arglists (meta #' println)) – GabiMe

-18
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" 
+5

No quiero * ejecutar * la función para conocer su arity. Y no quiero cambiar el código de función para este – GabiMe

47

reflexión disimulada:

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

O:

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

No funciona en macros – GabiMe

+13

Pero funciona en funciones anónimas. +1 –

+2

Esta parece ser la única opción para funciones anónimas, pero no me gusta la suposición de que el primer método declarado es el método de invocación. Yo cambiaría '(first (.getDeclaredMethods (clase f)))' en su lugar sería '(first (filter # (=" invoke "(.getName%)) (.getDeclaredMethods (clase f))))' –

3

Sobre la base de 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)))))) 
0

Mi opinión el problema aridad, sobre la base de 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)))) 
1

En realidad también trabaja 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.

0

Mi corazón sangró (cubrió 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) 
Cuestiones relacionadas