Functional programming Clojure:如何在运行时找出函数的算术性?

Functional programming Clojure:如何在运行时找出函数的算术性?,functional-programming,clojure,Functional Programming,Clojure,给定函数对象或名称,如何确定其算术性?类似于(arity func name) 我希望有一种方法,因为arity在Clojure中非常重要函数的arity存储在var的元数据中 user=> (defn test-func ([p1] "Arity was 1.") ([p1 p2] "Arity was 2.") ([p1 p2 & more-args] (str "Arity was " (+ 2 (count more-a

给定函数对象或名称,如何确定其算术性?类似于
(arity func name)


我希望有一种方法,因为arity在Clojure中非常重要

函数的arity存储在var的元数据中

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"
(:arglists (meta #'str))
;([] [x] [x & ys])
这需要使用
defn
或明确提供的
:arglists
元数据定义函数。

隐藏反射:

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


基于@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 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 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

我的心脏在流血(涵盖了所有情况)


我不想为了知道它的算术性而执行这个函数。我不想更改此注释的函数代码,这实际上只适用于使用defn定义的函数。它不适用于用fn或#()定义的匿名函数。据我所知,它也适用于所有内置函数。例如(:arglist(meta#'+)或(:arglist(meta#'println))核心函数属于“defn”类。例如,(def my-identity1(fn[x]x))将不起作用,而(defn my-identity2[x]x)将起作用。defn set:元数据中的arglist。核心函数可以手动使用defn或set:arglist。注意:它也不适用于多方法。您必须在那里手动设置:arglists:(defmulti-my-method{:arglists'([foo-bar])}(fn[&args](vec(映射类型args)))。因此,依赖:arglist充其量是有缺陷的。这似乎是匿名函数的唯一选项,但我不喜欢第一个声明的方法是invoke方法的假设。我将
(first(.getDeclaredMethods(class f))
改为
(first(filter#(=“invoke”(.getName%))(.getDeclaredMethods(class f)))
(defn arg-count [f]
  (let [m (first (.getDeclaredMethods (class f)))
        p (.getParameterTypes m)]
    (alength p)))

(defmacro my-macro [])

(arg-count @#'my-macro)
; 2
(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)