I have a function-defining function

SetAttributes[DefFn, HoldAll];

DefFn[f_[args___], body_] :=
 f[args] := body;

I am trying to modify this function to record the function name and the values of its arguments on a stack for error-reporting purposes. I have a function WithStackFrame which adds this information to a list, and I am inserting a call to this function into each definition:

DefFn[f_[args___], body_] :=
 f[args] := WithStackFrame[f,body];

This allows me to give a stack backtrace when reporting errors. This works. The problem comes when I try to store also the values of the function arguments. A naive

DefFn[f_[args___], body_] :=
 f[args] := WithStackFrame[{f,{args}},body];

does not work because args is actually the sequence of patterns. The closest I have got is

DefFn[f_[args___], body_] :=
    argValueExprs = {args} /. x_Pattern :> x[[1]];
    f[args] := WithStackFrame[{f,argValueExprs},body]];

For the purpose of testing, you can use

WithStackFrame[sf_, expr_] := Print[sf];

However, running this gives

DefFn[f[x_], x^2];


when I really want {f,{3}}. For some reason, the x is not being evaluated in a scope in which the function arguments are visible.

  • $\begingroup$ You might want to add a note that WithStackFrame needs attribute HoldAll for the test case to work as stated. $\endgroup$ – Albert Retey Feb 23 '12 at 16:28
  • $\begingroup$ @Albert I'd say, HoldFirst rather than HoldAll, to allow the body to evaluate. $\endgroup$ – Leonid Shifrin Feb 23 '12 at 16:36
  • $\begingroup$ @Leonid: actually now that I think about it even HoldFirst seems not really necessary, it was my mistake (I had a definition for x hanging around) that caused it was needed in the first place. Once the function does what it is supposed, the actual arguments will be used so the hold attribute should usually not be necessary at all, so it might be best to ignore my comment... $\endgroup$ – Albert Retey Feb 23 '12 at 17:22
  • $\begingroup$ @Albert Yes, but the arguments passed to the first argument of WithStackFrame, may be expressions which may evaluate (the function being executed may be HoldAll itself), so in that case HoldFirst for WithStackFrame would prevent that. $\endgroup$ – Leonid Shifrin Feb 23 '12 at 18:12

You could name the patterns

DefFn[f_[args___], body_] := 
  f[s : PatternSequence[args]] := WithStackFrame[{f, {s}}, body];
  • $\begingroup$ This is pretty cool. I use this myself often, but for this question, this did not cross my mind - too bad. Big +1. $\endgroup$ – Leonid Shifrin Feb 23 '12 at 16:26
  • $\begingroup$ @LeonidShifrin thanks. In an unrelated matter, I just found some behaviour that I found interesting and commented in the main chat but they had their minds somewhere else. I now discovered it's based on the Update[] issue but I would like to know what you think, if it would be useful, if you have a minute :) $\endgroup$ – Rojo Feb 23 '12 at 16:29
  • $\begingroup$ I like that for it's simplicity $\endgroup$ – Albert Retey Feb 23 '12 at 16:29
  • $\begingroup$ @Rojo You are a dangerous person :). You may find my answer in this thread relevant to your suggestion - I used there the one-step evaluation method similarly to your suggestion. $\endgroup$ – Leonid Shifrin Feb 23 '12 at 16:34
  • $\begingroup$ Thanks @LeonidShifrin... For the link and comment, not so much for the dangerous... A little limitation of using DownValues however is that they don't work well with Flat (which can't get any more complex). An advantage is that it isn't based on a bug, hehe $\endgroup$ – Rojo Feb 23 '12 at 16:49

You need a parser for the argument patterns. I wrote a simplistic one for this answer. I will reproduce it here to keep things self-contained:

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

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

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

With this, your code might be:

SetAttributes[DefFn, HoldAll];
DefFn[f_[args___], body_] :=
  With[{funargs = getFunArguments[HoldPattern[f[args]]]},
     With[{fargs = Join @@ getArgumentNames[funargs[[2]]]},
        SetDelayed @@ Hold[f[args], WithStackFrame[{f, fargs}, body]]]];

Using it:

DefFn[f[x_], x^2];


and thus:


 ==> {f,Hold[3]}


Here is a somewhat more general and more robust argument parser:

SetAttributes[parse, HoldAll];
parse[(Condition | PatternTest | Optional)[arg_, _]] := parse[arg];
parse[(HoldPattern | Optional)[arg_]] := parse[arg];
parse[Verbatim[Pattern][sym_, _]] := Hold[sym];
parse[Verbatim[Repeated][p_, ___]] := parse[p];
parse[(Blank | BlankSequence | BlankNullSequence)[___]] := Hold["NotAPatternVar"];
parse[(Longest | Shortest)[arg_, ___]] := parse[arg];
parse[Verbatim[PatternSequence][args___]] := parse[args];
parse[a_ /; AtomQ[Unevaluated[a]]] := Hold["NotAPatternVar"];
parse[args___] := Join @@ Map[parse, Unevaluated /@ Unevaluated[{args}]];
parse[f_[args___]] := {Hold[f], parse[args]};

With its help, the function would be written as:

SetAttributes[DefFn, HoldAll];
DefFn[f_[args___], body_] :=
  With[{fargs = parse[f[args]]},
     SetDelayed @@ Hold[f[args], WithStackFrame[fargs, body]]];

Some examples to try:

ClearAll[f, ff];
DefFn[f[x_], x^2];
  ff[x_,PatternSequence[Shortest[y__], p__] /; {Length[{p}] < Length[{y}]},z_, (q_) ..] /; 
         x < z, 
  {x, y, p, z, q}]

Note that the trick SetDelayed@@Hold[lhs,rhs] is needed to fool the variable renaming mechanism of enclosing With. This is explained in more detail here.

  • $\begingroup$ Faster and more elaborate than mine -- of course :-) $\endgroup$ – Albert Retey Feb 23 '12 at 16:27
  • $\begingroup$ @Albert You should remove this "of course" - I was faster by pure luck, and your solution is simpler. $\endgroup$ – Leonid Shifrin Feb 23 '12 at 16:28
  • $\begingroup$ You should also include Optional in your parser as it wraps Pattern like PatternTest does, e.g. FullForm[HoldPattern[a_:0]] returns HoldPattern[Optional[Pattern[a, Blank[]],0]. $\endgroup$ – rcollyer Feb 23 '12 at 16:35
  • $\begingroup$ @Leonid: I just deleted mine, since it just is a simpler version of yours. You might want to add an explanation that SetDelayed@@Hold[...] is just a trick to avoid the localization of SetDelayed that was the root of the problem that Ian has seen. $\endgroup$ – Albert Retey Feb 23 '12 at 16:37
  • $\begingroup$ @rcollyer Yes, you are right. Actually, the parser itself is not written well, because it should be true recursive decsent, while I took a short-cut and substituted it with repeated rule application through ReplaceRepeated. I will take some time and rewrite it, then update the answer - and incorporate your suggestion as well. $\endgroup$ – Leonid Shifrin Feb 23 '12 at 16:39

I would do this:

DefFn[f_[args___], body_] := 
  lhs : f[args] := WithStackFrame[lhs, body];

Then make WithStackFrame HoldFirst and do de-structuring there. For example:

SetAttributes[WithStackFrame, HoldFirst]

WithStackFrame[f_[args___], expr_] := Print[{f, {args}}];

If for some reason this were unacceptable I would do:

DefFn[f_[args___], body_] := 
  lhs : f[args] := WithStackFrame[{f, List @@ Unevaluated[lhs]}, body];

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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