How can I determine number of arguments to a function in Clojure?

后端 未结 2 921
暖寄归人
暖寄归人 2021-02-13 02:14

Given a function x in clojure how can I programatically get a count of the number of arguments?

eg:

(fn a [b c] ...) has has two arguments
(fn a [] ...)          


        
2条回答
  •  孤城傲影
    2021-02-13 02:47

    If you have access to the var that holds the function you can get the argument count by accessing its metadata in the following way:

    (defn arities [v]
      (->> v meta :arglists (map count)))
    
    (defn a [])
    (defn b [_ _])
    
    (map arities [#'a #'b])
    ;= ((0) (2))
    

    arities will return a seq with all the arities for the function. This has the disadvantage that for a variadic argument vector ([_ _ & _]) it will return (4).

    (defn c [_ _ & _])
    (arities #'c)
    ;= (4)
    

    This could be fixed by removing the & symbol from all the argument lists.

    (defn arities [v]
      (->> v 
        meta 
        :arglists 
        (map #(remove #{'&} %))
        (map count)))
    
    (arities #'c)
    ;= (3)
    

    If you don't have access to the var, the following is a little function that I've used to detect the argument count of a function. It uses reflection so it's not the approach you might want to take if you need good performance. Also take into account that it relies on implementation details.

    (defn n-args [f]
      (-> f class .getDeclaredMethods first .getParameterTypes alength))
    
    (defn a [])
    (defn b [_ _])
    (defn c [_ _ & _])
    
    (map n-args [a b c])
    ;= (0 2 3)
    

    EDIT

    After giving the answer another read, I realized the result 3 for a variadic function defined as (defn x [_ _ & _] ,,,), is actually quite misleading since it's the same result you would get for a function with 3 arguments. The following version will return :variadic, instead of a specific number, for the argument vectors that contain the & symbol (except for the case [&] where & it's the actual argument name). As mentioned in a comment by Jeremy Heiler getting the argument count from the metadata only works if the value for :arglists is not manually changed.

    (defn a [_])
    (defn b [_ _])
    (defn c [_ _ & _])
    (defn d [_ _ _])
    (defn e [&])
    
    (defn variadic? [s]
      (and (some #{'&} s)
           (not (every? #{'&} s))))
    
    (defn arities [v]
      (->> v
        meta
        :arglists
        (map #(if (variadic? %) :variadic %))
        (map #(if (sequential? %) (count %) %))))
    
    (map arities [#'a #'b #'c #'d #'e])
    ;= ((1) (2) (:variadic) (3) (:variadic))
    

    The reflection version for this is a little more complicated and it relies on more implementation details (i.e. "Is this or that function declared?" or "Does the function extend the class X?"), so I wouldn't recommend using that approach.

提交回复
热议问题