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:
Grab the r.h.s. of the definition and wrap it into Hold
:
step1 = Hold[Module[{}, checkArgs[args, currentFunction]]]
(* Hold[Module[{}, checkArgs[args, currentFunction]]] *)
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]]] *)
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
OptionValue
. $\endgroup$