Arity count

410 views
Skip to first unread message

lancecarlson

unread,
Aug 19, 2009, 4:24:04 AM8/19/09
to Clojure
Is there a way to introspect a function to get its arity? I know some
functions have an arbitrary amount of arguments, but knowing the count
on finite argument lists and whether or not a function accepts an
infinite list would be very useful. Also, hints on what types a
functions arguments accept would be nice to parse through. I don't
need the docs for particular functions, but to be able to use this
information in my code. Ideas?

Sean Devlin

unread,
Aug 19, 2009, 8:48:43 AM8/19/09
to Clojure
Check the metadata arglist. For example,

(:arglists (meta #'map))

If you want to automate this, you'll need a macro.

Meikel Brandmeyer

unread,
Aug 19, 2009, 8:53:13 AM8/19/09
to Clojure
Hi,

On Aug 19, 10:24 am, lancecarlson <lancecarl...@gmail.com> wrote:

> Is there a way to introspect a function to get its arity? I know some
> functions have an arbitrary amount of arguments, but knowing the count
> on finite argument lists and whether or not a function accepts an
> infinite list would be very useful.

I'm not aware, that such functionality exists. Also a function
might have many arities. Which to choose?

You might get that from metadata of functions stored in a Var.

user=> (:arglists (meta (var reduce)))
([f coll] [f val coll])

> Also, hints on what types a functions arguments accept would be
> nice to parse through. I don't need the docs for particular functions,
> but to be able to use this information in my code. Ideas?

You can't tell a priori, which types a function takes. For example:

(defn call-f-on
[f a b]
(f a b))

(call-f-on + 1 2)
(call-f-on dissoc {:foo #"bar" "yoyo" 'dyne} :foo)

What is the type signature of call-f-on? I mean something
more informative than:
call-f-on :: (Object -> Object -> Object) -> Object -> Object

As I (completely non-representative) never needed such
functionality, could you describe some use case for what
you have in mind?

Sincerely
Meikel

Achim Passen

unread,
Aug 19, 2009, 10:03:56 AM8/19/09
to clo...@googlegroups.com
Hi!

Inspecting the var's metadata is probably the best way to do this.

If you're not dealing with vars, but anonymous functions, here's a
hackish piece of code that gathers arity information via reflection:


(defn arities [f]
(let [methods (.getDeclaredMethods (class f))
count-params (fn [m] (map #(count (.getParameterTypes %))
(filter #(= m (.getName %))
methods)))
invokes (count-params "invoke")
do-invokes (map dec (count-params "doInvoke"))
arities (sort (distinct (concat invokes do-invokes)))]
(if (seq do-invokes)
(concat arities [:more])
arities)))


user> (arities map)
(2 3 4 :more)
user> (arities (fn ([a b]) ([a b c d]) ([a b c d e f & more])))
(2 4 6 :more)


Beware! This snippet relies on unexposed details of clojure's current
implementation. It might stop working tomorrow, so it's definitely not
intended for production use, but it might help with debbuging/exploring.

Kind Regards,
achim

John Harrop

unread,
Aug 19, 2009, 3:16:35 PM8/19/09
to clo...@googlegroups.com
On Wed, Aug 19, 2009 at 10:03 AM, Achim Passen <achim....@gmail.com> wrote:
Beware! This snippet relies on unexposed details of clojure's current
implementation. It might stop working tomorrow, so it's definitely not
intended for production use, but it might help with debbuging/exploring.

Meanwhile, for declared functions this works:

(map #(if (contains? (set %) '&) [:more (- (count %) 2)] (count %)) (:arglists ^#'foo))

giving results like:

(0 2 5 [:more 7])

(in this case for (defn foo ([] nil) ([glorb fuzzle] nil) ([x y z w u] x) ([a b c d e f g & more] more)))

Add this:

(defn accepts-arity [arities arity]
  (or
    (contains? (set arities) arity)
    (and (vector? (last arities)) (>= arity (second (last arities))))))

and you can check if the function accepts a particular arity. (This expects "arities" in the format output by my map expression. In particular, a list of numerical arities and possibly a [:more n] entry, which must be a vector and must be the last item in the list if present, and n must be the number of required parameters for the "& more" overload.)

Wrap it all up with two macros:

(defmacro fn-arities [fn-name]
  `(map (fn [x#] (if (contains? (set x#) '&) [:more (- (count x#) 2)] (count x#))) (:arglists ^#'~fn-name)))

(defmacro fn-accepts-arity [fn-name arity]
  `(accepts-arity (fn-arities ~fn-name) ~arity))

user=> (fn-accepts-arity foo 8)
true
user=> (fn-accepts-arity foo 6)
false
user=> (fn-accepts-arity reduce 2)
true
user=> (fn-accepts-arity reduce 3)
true
user=> (fn-accepts-arity reduce 4)
false
user=> (fn-arities map)
(2 3 4 [:more 4])

Works for macros, too:

user=> (fn-arities fn-arities)
(1)
user=> (fn-arities fn-accepts-arity)
(2)

But, as noted, only works with a name of a declared fn or macro:

user=> (fn-arities #(+ 3 %))
#<CompilerException java.lang.ClassCastException: clojure.lang.Cons cannot be cast to clojure.lang.Symbol (NO_SOURCE_FILE:110)>

(One thing odd about that:

user=> (class '#(+ 3 %))
clojure.lang.PersistentList

not Cons. Hmm.)

It also doesn't work with local, named functions, either using let or using letfn, even if (fn name [args] body) is used and not (fn [args] body):

user=> (let [x (fn [a] (+ 3 a))] (fn-arities x))
#<CompilerException java.lang.Exception: Unable to resolve var: x in this context (NO_SOURCE_FILE:117)>
user=> (let [x (fn x [a] (+ 3 a))] (fn-arities x))
#<CompilerException java.lang.Exception: Unable to resolve var: x in this context (NO_SOURCE_FILE:118)>
user=> (letfn [(x [a] (+ 3 a))] (fn-arities x))
#<CompilerException java.lang.Exception: Unable to resolve var: x in this context (NO_SOURCE_FILE:119)>

Riccardo Di Meo

unread,
Jul 25, 2016, 11:14:16 AM7/25/16
to Clojure, jharr...@gmail.com
Hi!
It's a very old thread, but is there any news about this? I mean, is there a way to find the list of accepted arities of an anonymous function, without trying them all and catching the exception?

Thanks!
 

Alex Miller

unread,
Jul 25, 2016, 11:40:09 AM7/25/16
to Clojure, jharr...@gmail.com
It might be helpful to back up a step and explain why you need to do this?

On Monday, July 25, 2016 at 10:14:16 AM UTC-5, Riccardo Di Meo wrote:
Hi!
Reply all
Reply to author
Forward
0 new messages