11

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_] :=
 Module[{argValueExprs,x},
    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];

f[3]  
(*
{f,{x}}
*)

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.

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
Ian Hinder
  • 2,125
  • 1
  • 14
  • 22
  • You might want to add a note that WithStackFrame needs attribute HoldAll for the test case to work as stated. – Albert Retey Feb 23 '12 at 16:28
  • @Albert I'd say, HoldFirst rather than HoldAll, to allow the body to evaluate. – Leonid Shifrin Feb 23 '12 at 16:36
  • @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... – Albert Retey Feb 23 '12 at 17:22
  • @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. – Leonid Shifrin Feb 23 '12 at 18:12

3 Answers3

11

You could name the patterns

DefFn[f_[args___], body_] := 
  f[s : PatternSequence[args]] := WithStackFrame[{f, {s}}, body];
Rojo
  • 42,601
  • 7
  • 96
  • 188
  • This is pretty cool. I use this myself often, but for this question, this did not cross my mind - too bad. Big +1. – Leonid Shifrin Feb 23 '12 at 16:26
  • @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 :) – Rojo Feb 23 '12 at 16:29
  • I like that for it's simplicity – Albert Retey Feb 23 '12 at 16:29
  • @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. – Leonid Shifrin Feb 23 '12 at 16:34
  • 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 – Rojo Feb 23 '12 at 16:49
  • @LeonidShifrin by the way, a couple of days ago I made a function that wraps definitions of Downvalues and such (in symbols already defined), to make those new definitions be "on top" in the downvalues list and not the default bottom. You know of any good way to do this other than prepending the downvalues by hand like I did? It seems like a common need – Rojo Feb 23 '12 at 16:52
  • @Rojo Been there, done that :) To my knowledge, you have to use Prepend, RotateRight, RotateLeft or the like. Sometimes this by otself is not enough, since they are reordered back. I use the trick of adding a symbol like f[x_]/;(tag;True):=r.h.s., so that the ordering engine can no longer find the relative generality. This is also handly because it is easy to remove those definitions, testing for the presence of the tag symbol in DownValues. You can localize tag with Module, if you want. – Leonid Shifrin Feb 23 '12 at 16:57
  • @LeonidShifrin good tips – Rojo Feb 23 '12 at 17:37
  • This would basically solve my problem, and was posted before Mr. Wizard's solution, but his is a bit cleaner so I'm actually using that. Anyway, thanks for all the help! StackExchange is amazing! – Ian Hinder Feb 24 '12 at 12:06
  • @IanHinder In that case, you should accept my answer. I realize this is to my own advantage in this case, but I also advocate selecting the best answer when it is not, and other major contributors have agreed with me. – Mr.Wizard Feb 24 '12 at 14:59
  • @Mr.Wizard, your answer basically rewrites my idea... Ok, in a cleaner and possibly less buggy way, and adds a suggestion of "rewriting the problem", changing "what he really wants" in a, I also agree, more natural way. Aren't the praises and the +1 of the accepted answerer enough satisfaction? Hehe ;) – Rojo Feb 24 '12 at 17:39
  • Rojo, I argue that my first method is in fact a separate idea that gets around the problem in a different way. If on the other hand Ian is using the Unevaluated method then I suggest deleting it from my answer and appending it to yours. – Mr.Wizard Feb 24 '12 at 17:52
  • BTW, I voted for yours, too. – Mr.Wizard Feb 24 '12 at 17:54
  • Oh no! I caused a ruckus! I didn't use the Unevaluated, as I used the more natural f[args] as an argument to the (held) WithStackFrame. I would say the original question was "how do I store the arguments in a stack", and the split into f and args was just my example of something I had tried and which had not worked. Mr Wizard's answer is the simplest and cleanest, which is why I use it, but I do feel it is almost a derivation of Rojo's. Anyway, I will go with the community consensus, if you can point me to it in the meta site? – Ian Hinder Feb 25 '12 at 14:29
  • @IanHinder haha, I don't think we care that much, and I agree with what's been said – Rojo Feb 25 '12 at 14:34
5

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[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
   };

With this, your code might be:

ClearAll[DefFn];
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];

?f
Global`f
f[x_]:=WithStackFrame[{f,Hold[x]},x^2]

and thus:

f[3]

(*
 ==> {f,Hold[3]}
*)

EDIT

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

ClearAll[parse];
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:

ClearAll[DefFn];
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];
DefFn[
  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.

Leonid Shifrin
  • 114,335
  • 15
  • 329
  • 420
  • Faster and more elaborate than mine -- of course :-) – Albert Retey Feb 23 '12 at 16:27
  • @Albert You should remove this "of course" - I was faster by pure luck, and your solution is simpler. – Leonid Shifrin Feb 23 '12 at 16:28
  • 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]. – rcollyer Feb 23 '12 at 16:35
  • @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. – Albert Retey Feb 23 '12 at 16:37
  • @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. – Leonid Shifrin Feb 23 '12 at 16:39
  • @Albert Ok, will add that. I will actually also rewrite the parser, and then update everything. – Leonid Shifrin Feb 23 '12 at 16:40
  • @rcollyer I added another implementation for the argument parser, which I like better. Probably not complete either, though. – Leonid Shifrin Feb 23 '12 at 19:27
4

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];
Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371