32
$\begingroup$

I have a newbie question: is it possible to write a function that counts the arguments (total and optionals) of a given function? Possibly it should be able to work with built-in and custom functions as well.

For example, if I define

f1[x_Integer] := x + 1;
f2[x_Integer, y_Integer: 1] := x + y;
g[x_Real, y_] := x - y;

I would like to have

countArgs[f1]
{1,0}
countArgs[f2]
{2,1}
countArgs[g]
{2,0}

and also, for example,

countArgs[Sin]
{1,0}

thank you.

@celtschk

Well, I didn't even know the use of UpValues, but basically what I am asking is the number of inputs the function needs, I don't care what the function does with those inputs. In your examples I would say

  • {Infinity,0} for foo? It's more a question than an answer, sorry, but I had not thought aboute these unusual cases.
  • this is nasty, I didn't think of a case like that either, I would say {3,{2}}, the {2} meaning exactly 2, to avoid bar[1,2], which is not legal.
  • {2,0} but just because you wrote f[g,g], so it's practically a guess, I don't know what are UpValues and if they go against the spirit of my question by messing with the function. I hope I was clear.
  • the last one I would say {3,0} as they were flattened.

Thank you, I start seeing that my question is not so obvious because there are too many complicated definitions for functions to take into account.

For now I understood that is possible with built-in functions with

SyntaxInformation[f]

(thank you Heike) but that mybe is a little too much asking for a general custom function.

$\endgroup$
14
  • 5
    $\begingroup$ For built in functions you can use SyntaxInformation. For example SyntaxInformation[Mod] returns {"ArgumentsPattern" -> {_, _, _.}} indicating 3 arguments of which the last one is optional. $\endgroup$
    – Heike
    Jun 19, 2012 at 14:33
  • 3
    $\begingroup$ What should it return for foo[x__Integer] := {x}? And what for bar[x_] := 1; bar[x_, y_, z_] := 2? Should it also consider UpValues like f[g,g] ^= 0? And would a[x_, {y_, z_}] := x+y/z count as taking two or three arguments? $\endgroup$
    – celtschk
    Jun 19, 2012 at 14:39
  • 1
    $\begingroup$ This becomes challenging, output for the following cases ? f[x_]:=1,f[x_,y_]:=1,f[x_,OptionsPattern[]] $\endgroup$ Jun 19, 2012 at 14:52
  • $\begingroup$ Then there are things like p[n_][x_] := (* stuff *)... $\endgroup$ Jun 19, 2012 at 15:03
  • $\begingroup$ @imnage_doctor In order I would say: - {1,0} - {2,0} - {1+n,n} assuming that OptionsPattern takes up to n optional values $\endgroup$
    – Jane T.
    Jun 19, 2012 at 15:04

3 Answers 3

18
$\begingroup$

Here is my attempt. The function below will work on functions with default args and options, as well as those having multiple definitions. I made the following assumptions:

  • Only DownValues - based definitions are considered
  • Default arguments, if present, are always to the right of mandatory arguments.
  • Options, if present, are always to the right of all other arguments, and are declared either by OptionsPattern[] or opts:OptionsPattern[] pattern.

Here is the code:

ClearAll[countArgs];
SetAttributes[countArgs, {HoldAll, Listable}];
countArgs[f_Symbol] := 
   With[{dv = DownValues[f]}, countArgs[dv]];

countArgs[Verbatim[HoldPattern][HoldPattern[f_Symbol[args___]]] :> _] :=
  countArgs[f[args]];

countArgs[
   f_[Except[_Optional | _OptionsPattern | 
         Verbatim[Pattern][_, _OptionsPattern]], rest___]] :=
  {1, 0, 0} + countArgs[f[rest]];

countArgs[ f_[o__Optional, rest___]] := 
  {0, Length[HoldComplete[o]], 0} +  countArgs[f[rest]];

countArgs[f_[_OptionsPattern | Verbatim[Pattern][_, _OptionsPattern]]] := 
  {0, 0, 1};

countArgs[f_[]] := {0, 0, 0};

This function represents a mini-parser for the function's declarations. It returns a list of 3-element sublists, of the length equal to a number of DownValues. In each sublist, the first number is a number of normal arguments, the second one is a number of default arguments, and the last one (which can only be 0 or 1), tells us whether or not there are options declared.

Some examples:

ClearAll[f1, f2, f3, f4, g]
f1[x_Integer] := x + 1;
f2[x_Integer, y_Integer: 1] := x + y;
f3[x_, y_, z_: 1, q_: 2, opts : OptionsPattern[]] := x + y + z + q;
f4[x_, y_: 1] := x + y;
f4[x_, y_, z_] := x + y + z;
g[x_Real, y_] := x - y;

Now applying our function:

countArgs /@ {f1, f2, f3, f4, g}
{{{1, 0, 0}}, {{1, 1, 0}}, {{2, 2, 1}}, {{1, 1, 0}, {3, 0, 0}},{{2, 0, 0}}}
$\endgroup$
17
  • $\begingroup$ Impressive! Thank you Leonid. Can I accept more than 1 answer? Yours truly deserves it. (PS. you typed two f4). $\endgroup$
    – Jane T.
    Jun 19, 2012 at 15:43
  • $\begingroup$ @Jane T. You can only accept one answer, and generally it is a good idea to wait a little bit before accepting any, so that people are motivated to write alternative answers. You can also unaccept an already accepted answer and accept another one if you like it better (please not that this is just generaly FYI, not an advice for this particular case). As for f4, this is intentional. In case you did not know, Mathematica functions can have more than one definition at the same time, and the one to be used is the one whose pattern will first match. $\endgroup$ Jun 19, 2012 at 15:47
  • $\begingroup$ @Leonid Shifrin Excellent :) Perhaps the 3rd element of the result might reflect Length@Options@f ? $\endgroup$ Jun 19, 2012 at 15:47
  • $\begingroup$ @image_doctor Thanks :). " Perhaps the 3rd element of the result might reflect Length@Options@f" - I don't think this is really necessary, but it could be a possibility (although Options might not be defined at the time when definitions are considered, although this is admittedly a rather contrived situation). $\endgroup$ Jun 19, 2012 at 15:49
  • $\begingroup$ @LeonidShifrin Perhaps counting options should be an option to countArgs ;) I think Options returns an empty list if the options for f aren't defined. Which I guess could be interpreted as logically correct from one viewpoint. $\endgroup$ Jun 19, 2012 at 15:54
9
$\begingroup$

This only works for simple cases such as those in the question:

countArgs[f_]:=Module[{countarg},
countarg[x___,y___Optional]:=Length/@{{x},{y}};
(DownValues[f]/.f->countarg)[[All,1,1]]]

E.g.

f1[x_]:=x+1;
f2[x_,y_:1]:=x+y;
g[x_,y_]:=x-y;
g[x_]:=x+1;

countArgs@f1
Out[6]= {{1,0}}

countArgs@f2
Out[7]= {{1,1}}

countArgs@g
Out[8]= {{2,0},{1,0}}

The example for g shows how it produces a list of answers for functions with more than one definition.

$\endgroup$
3
  • $\begingroup$ Thank you Simon, works perfectly. As others has showed, a general answer is asking too much. $\endgroup$
    – Jane T.
    Jun 19, 2012 at 15:23
  • $\begingroup$ I am new to stackexchange, so I was eager to vote for an answer as a sign of my appreciation, I was not aware it was a better custom to wait (as Leonid explained). Your answer is very good even if I have to choose Leonid. $\endgroup$
    – Jane T.
    Jun 19, 2012 at 16:09
  • $\begingroup$ @JaneT. Voting for many answers is fine, but you can only "accept" (click the "tick") for one answer. If several people give good answers, it is definitely fine to vote for them all (click the uparrow above the number at the top left of each answer), and "accept" the one that is most useful for you. And a belated welcome to Mathematica.StackExchange!! $\endgroup$
    – Verbeia
    Jun 29, 2012 at 11:25
6
$\begingroup$

This is not intended as an answer, or not at the moment. I just thought it was worth mentioning a built-in that is documented but not in the Doc Center

ArgumentCountQ[head, len, min, max] tests whether the number len of arguments of a function head is between min and max.

ArgumentCountQ[head,len,{Subscript[m, 1],Subscript[m, 2],...,Subscript[m, i]}] tests whether the number len of arguments of a function head is one of the Subscript[m, i].

EDIT

Ok, this is totally useless to the question, but for now I'm leaving this since people seem to like to learn new functions.

This is the predicate that normally issues the famous message xxx called with 2 arguments; X arguments are expected.. It receives the head, the number of arguments input into the expression, and the number of expected arguments, either as a range or as a list of possible numbers. If the number of arguments is right, it returns True. If it's not, it returns False and issues a message. Furthermore, there's the function System`FEDump`NonOptionArgCount possibly useful for argument counting. A sample of use could be

f2[i___, OptionsPattern[]] := {i} /; 
  ArgumentCountQ[f, System`FEDump`NonOptionArgCount[{i}], {2}]

A perhaps less simple but more common use case could be

f[i_, j_, OptionsPattern[]] := i + j
f[i___] := (ArgumentCountQ[f, 
   System`FEDump`NonOptionArgCount[{i}], {2}]; Null /; False)

The first definition is your usual function definition. The second one catches all, and in all cases runs the first argument of the CompoundExpression: ArgumentCountQ[f, System`FEDump`NonOptionArgCount[{i}], {2}]. This gives True if the number of arguments passed, not counting the options, is exactly 2, but we don't care about that. We only care about the message it issues when it's not. After that, the definition is not applied since there's a hard-coded condition set to False, so it returns unevaluated.

$\endgroup$
11
  • $\begingroup$ Can you give an example of that in use? $\endgroup$
    – Mr.Wizard
    Jun 20, 2012 at 1:28
  • $\begingroup$ @Mr.Wizard, check the edit $\endgroup$
    – Rojo
    Jun 20, 2012 at 1:52
  • $\begingroup$ Thanks. I'm torn about voting; on the one hand I think it's cool you're digging this up, on the other I don't think it answers the question, as you said. I think I have to go with the latter, but consider yourself +1 on real reputation. :-) $\endgroup$
    – Mr.Wizard
    Jun 20, 2012 at 2:12
  • $\begingroup$ @Mr.Wizard, it's ok, this one was for solidarity, I wasn't expecting votes. I'm writing an answer to the return's second argument question and I expect upvotes there ;) $\endgroup$
    – Rojo
    Jun 20, 2012 at 2:56
  • 1
    $\begingroup$ You will like Developer`CheckArgumentCount than System`FEDump`NonOptionArgCount $\endgroup$
    – yode
    Dec 13, 2016 at 9:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.