23
$\begingroup$

I would like to know the name of the current function from within that function. For example, consider the following code

myFunction[args___] :=
  Module[{},
    checkArgs[args,"myFunction"];
    someCode[]
  ];

where checkArgs is used in several functions which have the same types of arguments and reports an error message which includes the name of the function it was called from. I would like to call checkArgs without having to give it the name of the function.

The evaluation stack, obtained using Stack, I believe does not contain this information unless called from within a StackComplete, and this is generally not the case in normal use of the function.

I appreciate that "the current function" is not necessarily a well-defined concept in Mathematica, since you can have nested function definitions, and a function is just a rule definition anyway. However, the options management system uses this concept. If I have OptionsPattern in the function definition, I can call OptionValue, and something knows to look in Options[myFunction] to find the information about the options. So could whatever this mechanism is using be accessed? Is there a way to get the current function from the options system?

The best solution I have come up with is this:

(fn:myFunction)[args___] :=
  Module[{},
    checkArgs[args,fn];
    someCode[]
  ];

which avoids having to pass in the function name explicitly, but is still quite ugly.

$\endgroup$
2
  • $\begingroup$ Apart from being ugly, what shortcomings does your workaround solution have? I'm not sure that Leonid's answer is not an overkill if the only real difference is uglyness. Nevertheless, would be nice to have a built-in solution a la OptionValue. $\endgroup$ Aug 3, 2016 at 20:41
  • 1
    $\begingroup$ A related thread. $\endgroup$ Aug 7, 2016 at 20:53

3 Answers 3

16
$\begingroup$

Custom assignement operators

Not quite what you asked for, but (as we already discussed recently), you can use custom assignment operators to define some variable that would have the value set to the name of your function inside its body. Here is a possibility:

ClearAll[def];
SetAttributes[def, HoldAll];
def /: SetDelayed[def[f_[args___]], rhs_] :=
   f[args] := Block[{$inFunction = f}, rhs];
Protect[def];

The above assignment operator is based on dynamic scoping. In case you want to bind lexically, you can do a similar thing, which in this case becomes a macro:

ClearAll[deflex];
SetAttributes[deflex, HoldAll];
deflex /: SetDelayed[deflex[f_[args___]], rhs_] :=
   Hold[rhs] /. HoldPattern[currentFunction] :> 
       f /. Hold[code_] :> SetDelayed @@ Hold[f[args], code];
Protect[deflex]

In contrast to def, the deflex operator does not wrap Block[{$inFunction = f}, ...] around the body of your function, but rather replaces all literal occurrences of currentFunction in the body of your function with f, before making a definition.

Example

The above form allows for a natural-looking code:

def @ myFunction[args___] :=
   Module[{}, checkArgs[args, $inFunction]]

so that you just have to add def @ to the usual definition. Here is the generated defintion:

?myFunction
Global`myFunction
myFunction[args___]:=
    Block[{$inFunction=myFunction},Module[{},checkArgs[args,$inFunction]]]

And here is a test:

myFunction[1,2,3]

(* checkArgs[1,2,3,myFunction] *)

You can do a similar thing with deflex:

ClearAll[mySecondFunction];
deflex @ mySecondFunction[args___] :=
   Module[{}, checkArgs[args, currentFunction]]

although the generated definition will be different:

?mySecondFunction
Global`mySecondFunction
mySecondFunction[args___]:=Module[{},checkArgs[args,mySecondFunction]]

In this case, both definitions efectively result in a similar run-time behavior, but there can be situations where this will not be the case.

How it works

Per request, let's break down the way how these operators work. I will consider deflex only, as a more complex one.

The first observation which follows directly from the definition of deflex is that it works at definition-time. Consider a simplified one:

ClearAll[deftest];
SetAttributes[deftest, HoldAll];
deftest /: SetDelayed[deftest[f_[args___]], rhs_] := {Hold[f[args], Hold[rhs]]}

Let's see what happens when we use it:

deftest @ f[x_, y_] := x + y

(* {Hold[f[x_, y_], Hold[x + y]]} *)

You can see that no global rules were created for the symbol f:

DownValues[f]

(* {} *)

because the UpValue for deftest has completely overridden how SetDelayed works on deftest[f_[args___]] (in this particular case, deftest[f[x_,y_]]. So using this UpValue, we get the control from SetDelayed when it executes on any l.h.s. of the form deftest[func_[x___]], and can then do with the l.h.s. and r.h.s. whatever we want.

Now consider the definition again:

ClearAll[mySecondFunction];
deflex @ mySecondFunction[args___] := 
  Module[{}, checkArgs[args, currentFunction]]

Let's clear the definition again first:

 ClearAll[mySecondFunction]

Here are the steps that the execution goes through:

  1. Grab the r.h.s. of the definition and wrap it into Hold:

    step1 = Hold[Module[{}, checkArgs[args, currentFunction]]]
    
    (* Hold[Module[{}, checkArgs[args, currentFunction]]] *)
    
  2. Replace all occurrences of the symbol currentFunction with the actual symbol to which we assign the definition (DownValue) - we know it since we have destructured the l.h.s. into pattern variables f and args:

    step2 = step1 /. HoldPattern[currentFunction] :> mySecondFunction
    
    (* Hold[Module[{}, checkArgs[args, mySecondFunction]]] *)
    
  3. Now I will introduce an additional step to clarify what is going on: instead of using SetDelayed on the last step, let's first use RuleDelayed, which is very similar except it will not create a global rule but rather form a local rule. And also, let's first do it in a naive way, just using RuleDalayed directly:

    step3 = step2 /. Hold[code_] :> 
       RuleDelayed[mySecondFunction[args___], code]
    
    (* mySecondFunction[args$___] :> Module[{}, checkArgs[args, mySecondFunction]] *)
    

    The idiom that was used here is called injector pattern on this site. The idea behind it is to inject some unevaluated pieces of code into larger expressions - see the link for an example, and you can see more example by doing the search

    So basically, we have assembled the final definition at this step, where the l.h.s. has been unchanged, but in the r.h.s. we have already replaced the symbol currentFunction by the actual head of the function being defined.

    There are only two differences w.r.t. the original code:

    a. We used RuleDelayed instead of SetDelayed to illustrate what we get back (since SetDelayed would've just created this very rule as a global one and return back Null)

    b. We still have a problem: if you look carefully at the result, we got the arguments in the pattern renamed, on the l.h.s.: args$___, while kept as they are, on the r.h.s. (args). This is not what we want, since such a rule will not work - we want the pattern variable to match the r.h.s.

    To do that, we have to fool the built-in variable renaming mechanism, which uses renaming to protect the inner lexical scoping constructs. The general ways to do that were described here. One of the simplest ways is to replace innerScopingConstruct[args] with innerScopingConstruct @@ Hold[args] (see the mentioned link for explanations). So the correct third step would've been:

    step3Correct = step2 /. Hold[code_] :> 
       RuleDelayed @@ Hold[mySecondFunction[args___], code]
    
    (* mySecondFunction[args___] :> Module[{}, checkArgs[args, mySecondFunction]] *)
    

    where now no renaming has taken place and the pattern arg___ on the l.h.s. matches the use of arg on the r.h.s.

What remains is to simply replace RuleDelayed by SetDelayed, and that would create a new / corrected global rule / definition, and attach it to mySecondFunction as a DownValue.

So basically, what deflex is doing is this:

  • Intercepts the original call of SetDelayed on the l.h.s. of the form deflex[func_[x___]]
  • Transforms the r.h.s. of that original definition by replacing all occurrences of symbol currentFunction with the actual value of func, while keeping the code held
  • Forms a new definition and calls SetDelayed again, but this time directly on the l.h.s. without prepending deflex to it - thus allowing SetDelayed to execute "normally" on the modified definition and create a global rule.

So deflex really works somewhat similar to decorators in Python, since we end up with a modified definition. in fact, in this answer I have used a deflex - like construct to implement decorators, in exactly the same way.

Limitations

Note that this form of def (or deflex) can not handle definitions of this type:

def @ myFunction[args___] /; Length[{args}] > 1 :=
   Module[{}, checkArgs[args, $inFunction]]

but can handle a similar one like this:

def @ myFunction[args___] :=
   Module[{}, checkArgs[args, $inFunction]] /; Length[{args}] > 1
$\endgroup$
13
  • 2
    $\begingroup$ A bit OT, but I always wondered: How does the magic behind OptionValue work? $\endgroup$
    – sebhofer
    Mar 4, 2013 at 17:11
  • 1
    $\begingroup$ @sebhofer The best I can tell at the moment is what is described in my answer to this question. $\endgroup$ Mar 4, 2013 at 17:25
  • $\begingroup$ Both solutions look amazing! IMO the later produces a more concise result; the former solution is brilliant in conception, the later one I really am not sure how it works; pure magic as far as I'm concerned. I'm really happy I saw these; they made my day $\endgroup$ Apr 4, 2019 at 5:41
  • $\begingroup$ @yosimitsukodanuri Well, they are similar, except the latter one processes the code of the body of the function, adding the function name at definition time, while the former wraps the body in Block, making a special variable available at run-time. In some cases, either one can be used, in others one might be preferable. In any case, glad you find them useful. $\endgroup$ Apr 4, 2019 at 5:44
  • $\begingroup$ @Leonid Shifrin I get how the Block one works; I hope I'll understand the RuleDelayed within the month; brilliant! thank you $\endgroup$ Apr 4, 2019 at 6:33
13
$\begingroup$

If a current evaluation rule contains/uses OptionsPattern[] then you can abuse the fact that OptionName[a] is replaced by OptionName[functionName, {}, a].

foo[OptionsPattern[]] := Hold[OptionValue[whatever]][[1, 1]]

(*DownValue: HoldPattern[foo[OptionsPattern[]]] :> Hold[OptionValue[whatever]][[1, 1]] *)

foo[]

foo

$\endgroup$
2
  • 3
    $\begingroup$ Oh, that's a neat trick! Thanks for sharing. $\endgroup$ Apr 2, 2019 at 21:19
  • $\begingroup$ @SjoerdSmit yep, it is interesting $\endgroup$
    – Kuba
    Apr 13, 2019 at 7:11
7
$\begingroup$

Well, you can exploit OptionValue for this purpose, but of course this is not the answer you are looking for:

ClearAll[myFunction, checkArgs, someCode];
Options[myFunction] = {"Name" -> myFunction};
myFunction[arg_, OptionsPattern[]] := Module[{},
   checkArgs[arg, OptionValue@"Name"];
   someCode[];
   ];

Downside is that you have to maintain options. Also, you cannot really prevent something passing on a fake "Name"->otherFunction argument to your function. If all your functions have a "Name" option then at least you don't have to make the checkArgs call function-specific.

Of course you can use "$$ObscureOptionUserShouldNotMeddleWith643x84" instead of "Name".

$\endgroup$

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.