8
$\begingroup$

Suppose that I define

f = Function[#1 + #2];

And I want to define a new function, argsNum, so that it returns a number of arguments of input function:

argsNum[f]
2

since f has 2 slots, #1 and #2.

I found the good Q&A, Function that counts the number of arguments of other functions but it does not work on functions of form like Function[#1 + #2] or etc.

It works on Function[{x, y}, x + y]. But I need a function that works on slots.

How can I modify the function in above link or define a new function which also works on slots?

$\endgroup$
5
  • 1
    $\begingroup$ Count[f, _Slot, ∞] $\endgroup$
    – m_goldberg
    Commented Aug 5, 2014 at 16:13
  • 2
    $\begingroup$ Can you explain why you need this? For reasons that Mr. Wizard mentions in his answer, I think that such a function wouldn't be needed most of the time. But of course I might be wrong. If you explain why you need this, we might be able to give a better answer (and people would also be more interested in the question). As MrW. said, pure functions can take any number of arguments that is greater than the largest slot number. The new named slots introduced in v10 complicate this further (e.g. #one + #two &) $\endgroup$
    – Szabolcs
    Commented Aug 5, 2014 at 17:04
  • $\begingroup$ @m_goldberg What about repeated use of the same Slot? Even in the most narrow application that won't work. $\endgroup$
    – Mr.Wizard
    Commented Aug 5, 2014 at 17:13
  • $\begingroup$ @Szabolcs Actually I am making a function with one argument and that argument would be another function that I want to check. To make this explanation be simple, GoodFuncQ[func] returns true if func has exactly 2 arguments, otherwise, false. $\endgroup$
    – Analysis
    Commented Aug 5, 2014 at 19:41
  • 1
    $\begingroup$ @Analysis Thanks for the Accept. Considering your application I think it would be better to Check for error messages during application, as I suspect such tests cannot be written robustly for all possible input, at least not without unreasonable overhead. Perhaps one of the argument testing frameworks I presented here will be useful to you. $\endgroup$
    – Mr.Wizard
    Commented Aug 5, 2014 at 20:23

4 Answers 4

18
$\begingroup$

Your question is not well specified for several reasons:

  1. Pure functions accept a flexible number of arguments

    #1 + #2 &[a, b, c, d]
    
    a + b
    
  2. It is common for some arguments to not be used:

    #1 + #3 & @@ {a, b, c, d}
    
    a + c
    
  3. SlotSequence includes all arguments after the given position:

    +##3 & @@ {a, b, c, d}
    
    c + d
    

Without clarifying how each of these cases is to be counted it is impossible to give a general answer.

$\endgroup$
5
  • 3
    $\begingroup$ +##&, seriously? Not that it doesn't amuse me, it's not something I would consider writing. (+1) $\endgroup$
    – rcollyer
    Commented Aug 5, 2014 at 17:56
  • 1
    $\begingroup$ @rcollyer It's a long-time favorite of mine. :D Actually I like it so much I wish there were more short-form operators that would work this way. (Monadic.) $\endgroup$
    – Mr.Wizard
    Commented Aug 5, 2014 at 18:00
  • $\begingroup$ Mr.Wizard, can you explain how the evaluation of (3) works? Why is + special in this respect and are there other operators which work in the same way? $\endgroup$
    – sebhofer
    Commented Aug 6, 2014 at 14:31
  • $\begingroup$ @sebhofer +x parses as Plus[x]. + is special because most Mathematica operators do not have a monadic form; I cannot write *x for Times[x] for example. I can write 1*## which parses as Times[1, ##] however. I wrote some about this here; please take a look at that before asking follow-up questions. $\endgroup$
    – Mr.Wizard
    Commented Aug 6, 2014 at 17:02
  • $\begingroup$ That makes sense. Don't know why I didn't think of that. Sorry. I only realized that *## doesn't work (and -## neither for that matter). Thanks for the link, I wasn't aware of that post. I looked for "monad" but came up empty handed. $\endgroup$
    – sebhofer
    Commented Aug 6, 2014 at 18:32
9
$\begingroup$

As @Mr.Wizard has pointed out, your question isn't well specified. However, a pure function always needs a minimum number of arguments, otherwise an error message is thrown:

#1 + #2 &[a]

Function::slotn: Slot number 2 in #1+#2& cannot be filled from (#1+#2&)[a] >>

a + #2

So finding the minimum number of required arguments of a pure function is a well-posed problem. We can solve it by simply detecting if the above error message is thrown for a given number of arguments; if so, we increase the number of arguments and try again:

EnoughArgumentsQ[f_Function, numargs_Integer] := 
  Quiet[
    Check[
      f @@ ConstantArray[Null, numargs]; True, 
      False, 
      Function::slotn
    ],
    Function::slotn
  ];

MinNumberOfArguments[f_Function] := 
  NestWhile[# + 1 &, 0, ! EnoughArgumentsQ[f, #] &]

This is not a very efficient approach, but it gets the job done in a pretty robustly fashion:

MinNumberOfArguments /@ {
  (* 0 *)
  Function[{}, Null],
  (* 1 *)
  MapIndexed[#1^#2 &, #] &,
  (* 2 *)
  #1 + #2 &,
  (* 3 *)
  {#1,#2,#3,##} &,
  (* 4 *)
  #1 + #4 &
}
{0,1,2,3,4}

Mathematica is bound to have a cleverer internal way of determining the minimum number of arguments before it throws the Function::slotn message. If anybody knows how this is done, and if it's accessible, that'd be great :).

$\endgroup$
4
  • $\begingroup$ Interesting, because you used Check it even gets the pathological Map[Function[{x, y}, {#1, #2, x, y}] &, #] &. $\endgroup$
    – rcollyer
    Commented Aug 5, 2014 at 18:06
  • $\begingroup$ I'd even consider reducing possible run-time of this by first extracting Slot/SlotSequence, i.e. Cases[f, (Slot|SlotSequence)[n_]:> n, -1], and running down from n. Also, I'd use NestWhile as it returns an expression. $\endgroup$
    – rcollyer
    Commented Aug 5, 2014 at 18:12
  • $\begingroup$ @rcollyer I'm not sure what you mean with "it returns an expression", but I've changed MinNumberOfArguments to use NestWhile. Thanks for the suggestion! $\endgroup$ Commented Aug 5, 2014 at 18:22
  • $\begingroup$ While, For, Do, and Scan do not have return values. NestWhile, Table, etc. do. So, your prior code where you had to write While[...]; i is not needed as you can write NestWhile[...] directly. $\endgroup$
    – rcollyer
    Commented Aug 5, 2014 at 19:00
4
$\begingroup$

Here's my approach to determine the number of slots in the pure function without counting those in any sub-functions. That means MapIndexed[#1^#2 &, #1] & (per @TeakeNutma's comment in @JohnMcGee's answer) will only return one slot. I do this by deleting all inner Functions before counting the slots:

Clear[slotCount]
slotCount[f_Function]:=Module[{deleteInner},
deleteInner=Replace[f,_Function:>Sequence[],{1,Infinity}];
DeleteDuplicates[Cases[deleteInner,_Slot,{1,Infinity},Heads->True]]//Length
]

Notes: Using Heads->True for Cases will count slots correctly for cases like #1[#2].


Testing on the example functions in @TeakeNutma's answer

{#,slotCount[#]}&/@{Function[{},Null],MapIndexed[#1^#2&,#]&,#1+#2&,{#1,#2,#3,##}&,#1+#4&}//Grid[#,Frame->All]&

Mathematica graphics

I'm not quite sure #1 + #4 & would have 4 slots (instead of just 2). Below is a modified version of my slotCount function that will return 4:

Clear[slotCountPositionAware]
slotCountPositionAware[f_Function]:=Module[{deleteInner,slotList,maxSlot},
deleteInner=Replace[f,_Function:>Sequence[],{1,Infinity}];
slotList=DeleteDuplicates[Cases[deleteInner,_Slot,{1,Infinity},Heads->True]];
maxSlot[l_List]:=If[l=={},0,l/.Slot[x_]:>x//Max];
maxSlot[slotList]
]

{#,slotCountPositionAware[#]}&/@{Function[{},Null],MapIndexed[#1^#2&,#]&,#1+#2&,{#1,#2,#3,##}&,#1+#4&}//Grid[#,Frame->All]&

Mathematica graphics

$\endgroup$
4
  • 1
    $\begingroup$ This method does not like this function for input: Function[{x, y}, {#1, #2, x, y}] &. (That's a function that returns a Function.) $\endgroup$
    – Mr.Wizard
    Commented Aug 6, 2014 at 20:10
  • $\begingroup$ I've never seen mixing of named slots and hashtag(?) slots in a Function before. Isn't #1 the same thing as x and #2, y? I'm working on my function to see if it could accommodate such cases, hopefully with some success. $\endgroup$ Commented Aug 6, 2014 at 21:02
  • $\begingroup$ @Mr.Wizard I've defined another instance of slotCount to handle special functions like the one above. Let me know what you think and where I can improve: Clear[slotCount] slotCount[f:Function[Function[__]]]:= Module[{deleteInner}, deleteInner=Replace[f,_Function:>Sequence[],{2,Infinity}]//First; Cases[deleteInner,_Slot,{1,Infinity},Heads->True]~Join~First@deleteInner//Length ]; slotCount[Function[{x,y},{#1,#2,x,y}]&] $\endgroup$ Commented Aug 6, 2014 at 21:12
  • $\begingroup$ That example was borrowed from a comment by rcollyer. It is not mixing Slot and named parameters in the same Function, but rather one function nested inside another like this. The outer function using & and # contains the inner function using Function and x/y. Using the entire thing as fn, fn[1, 2] yields Function[{x, y}, {1, 2, x, y}] which is itself a complete function. $\endgroup$
    – Mr.Wizard
    Commented Aug 6, 2014 at 21:37
1
$\begingroup$

Try

Length@Union@Cases[f, _Slot, Infinity]
2

The Union eliminates duplicates

$\endgroup$
2
  • 3
    $\begingroup$ This doesn't work for cases like f = MapIndexed[#1^#2 &, #] &, which only has one argument. $\endgroup$ Commented Aug 5, 2014 at 16:29
  • 1
    $\begingroup$ This would fail for f = #1 + #3 & $\endgroup$
    – Anti Earth
    Commented Jul 27, 2017 at 9:44

Your Answer

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

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