2009-11-08 14 views

Odpowiedz

48

arity z funkcji są przechowywane w metadanych var.

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

Wymaga to funkcja została zdefiniowana za pomocą defn lub metadane :arglists dostarczane bezpośrednio.

+0

. Wielkie dzięki – GabiMe

+2

Zauważ, że to naprawdę działa tylko dla funkcji zdefiniowanych z defn. Nie działa dla anonimowych funkcji zdefiniowanych za pomocą fn lub #(). – kotarak

+0

O ile widzę, działa również dla wszystkich funkcji wbudowanych. Na przykład (: arglists (meta # '+)) lub (: 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

Nie chcę * wykonywać * funkcji, aby poznać jej stan. I nie chcę zmieniać kodu funkcji dla tego znakomitego – GabiMe

47

podstępne odbicie:

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

Lub:

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

Nie działa na makrach – GabiMe

+13

Ale działa na anonimowych funkcjach. +1 –

+2

To wydaje się być jedyną opcją dla anonimowych funkcji, ale nie podoba mi się założenie, że pierwszą deklarowaną metodą jest metoda invoke. Chciałbym zmienić '(najpierw (.getDeclaredMethods (klasa f))) zamiast tego będzie ' (najpierw (filtr # (= "wywołać" (.getName%)) (.getDeclaredMethods (klasa f))))) –

3

Opierając się na użytkownika @ whocaresanyway rozwiązanie:

(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

My się na problem arity, opierając się na innych rozwiązań:

(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

Właściwie to działa również na makr:

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

(defmacro my-macro []) 

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

Dlaczego 2? Ponieważ każde makro ma dwa niejawne argumenty odpowiednio: &form i &env.

0

Moje serce krwawiło (pokryło wszystkie przypadki).

(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) 
Powiązane problemy