Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hold any argument

Is it possible to define a function that holds arguments at given positions ?

Or to do something like HoldLast as a counterpart to HoldFirst ?

like image 900
faysou Avatar asked Aug 31 '11 11:08

faysou


1 Answers

As far as I know, you can not do this directly in the sense that there isn't a HoldN attribute. However, below there is a work-around that should be doing what you requested.


Proposed solution

One simple way is to define an auxiliary function that will do the main work, and your "main" function (the one that will actually be called) as HoldAll, like so:

In[437]:= 
SetAttributes[f, HoldAll];
f[a_, b_, c_] :=
   faux[a, Unevaluated[b], c];
faux[a_, b_, c_] := Hold[a, b, c]

In[440]:= f[1^2, 2^2, 3^2]
Out[440]= Hold[1, 2^2, 9] 

You don't have to expose the faux to the top level, can wrap everyting in Module[{faux}, your definitions] instead.


Automation through meta-programming

This procedure can be automated. Here is a simplistic parser for the function signatures, to extract pattern names (note - it is indeed simplistic):

splitHeldSequence[Hold[seq___], f_: Hold] := List @@ Map[f, Hold[seq]];

getFunArguments[Verbatim[HoldPattern][Verbatim[Condition][f_[args___], test_]]] := 
     getFunArguments[HoldPattern[f[args]]];

getFunArguments[Verbatim[HoldPattern][f_[args___]]] := 
     FunArguments[FName[f], FArgs @@ splitHeldSequence[Hold[args]]];

(*This is a simplistic "parser".It may miss some less trivial cases*)

getArgumentNames[args__FArgs] := 
   args //. {
     Verbatim[Pattern][tag_, ___] :> tag, 
     Verbatim[Condition][z_, _] :> z, 
     Verbatim[PatternTest][z_, _] :> z
   };

Using this, we can write the following custom definition operator:

ClearAll[defHoldN];
SetAttributes[defHoldN, HoldFirst];
defHoldN[SetDelayed[f_[args___], rhs_], n_Integer] :=
   Module[{faux},
      SetAttributes[f, HoldAll];
      With[{heldArgs = 
         MapAt[
            Unevaluated,
            Join @@ getArgumentNames[getFunArguments[HoldPattern[f[args]]][[2]]],
            n]
         },
        SetDelayed @@ Hold[f[args], faux @@ heldArgs];
        faux[args] := rhs]]

This will analyze your original definition, extract pattern names, wrap the argument of interest in Unevaluated, introduce local faux, and make a 2-step definition - basically the steps we did manually. We need SetDelayed @@ .. to fool the variable renaming mechanism of With, so that it won't rename our pattern variables on the l.h.s. Example:

In[462]:= 
ClearAll[ff];
defHoldN[ff[x_,y_,z_]:=Hold[x,y,z],2]

In[464]:= ?ff
Global`ff
Attributes[ff]={HoldAll}

ff[x_,y_,z_]:=faux$19106@@Hold[x,Unevaluated[y],z]

In[465]:= ff[1^2,2^2,3^2]
Out[465]= Hold[1,2^2,9]

Notes

Note that this is trivial to generalize to a list of positions in which you need to hold the arguments. In general, you'd need a better pattern parser, but the simple one above may be a good start. Note also that there will be a bit of run-time overhead induced with this construction, and also that the Module-generated auxiliary functions faux won't be garbage-collected when you Clear or Remove the main ones - you may need to introduce a special destructor for your functions generated with defHoldN. For an alternative take on this problem, see my post in this thread (the one where I introduced the makeHoldN function).

like image 101
Leonid Shifrin Avatar answered Nov 09 '22 23:11

Leonid Shifrin