3

I want to write a custom sow/reap pair to wrap any piece of code in sow[code] and call reap to collect the timing of code with a tag that is the completely unevaluated version of code. My proble is that I cannot effectively withhold argument value substitution. I expect to use this pair only on my own functions fun so I can freely get and set DownValues as I like. Of course I expect to put many sow in my definition of fun. See example:

ClearAll[sow, reap, fun];

Attributes[reap] = {HoldAllComplete};
reap[x_] := Module[{h = (Hold@x)[[1, 0]], old, new, res},
   old = DownValues[Evaluate@h]; (* store old DownValues *)
   new = old /. {sow[y_] :> 
       Block[{time, out}, {time, out} = AbsoluteTiming@y; 
        Sow[ToString@Unevaluated@y -> time]; out]};
   DownValues[Evaluate@h] = new;(* install new DownValues *)
   res = Reap@x;
   DownValues[Evaluate@h] = old;(* restore old DownValues *)
   res];

fun[i_Integer] := sow@(Print["CALLED"]; Table[None, {i}]);

Now call reap on fun:

In[1]:= reap[fun[2]]

During evaluation of In[1]:= CALLED

Out[1]= {{None, None}, {{"Print[CALLED]; Table[None, {2}]" -> 0.0000418147}}}

This is almost what I want, but not exactly. How can I keep i from evaluating to the argument value 2? That is, I want to have "Print[CALLED]; Table[None, {i}]" instead of "Print[CALLED]; Table[None, {2}]" in the resulting tag. I guess this cannot be solved locally within any definition of sow, hence the DownValue-manipulation within reap. Can it be solved with the injector pattern or the Trott-Strzebonski in-place evaluation?

I know that I can specify tags manually in Sow but I specifically want to avoid this and simply use sow as a one-argument function.

István Zachar
  • 47,032
  • 20
  • 143
  • 291

1 Answers1

3

You need to stringify or otherwise protect the argument of sow outside of the usual down-value application (when the down-value is evaluated, the substitution is already made as you've noticed). This can easily be done using the Trott-Strzebonski in-place evaluation mentioned in the question:

ClearAll[sow, reap, fun];

Attributes[reap] = {HoldAllComplete};
reap[x_] := Module[{h = (Hold@x)[[1, 0]], old, new, res},
   old = DownValues[Evaluate@h]; (* store old DownValues *)
   new = old /. sow[y_] :> 
    With[
     {tag=ToString@Unevaluated@y},
       Module[{time, out}, 
        {time, out} = AbsoluteTiming@y; 
        Sow[tag -> time];
        out
       ]/;True
    ];
   DownValues[Evaluate@h] = new;(* install new DownValues *)
   res = Reap@x;
   DownValues[Evaluate@h] = old;(* restore old DownValues *)
   res];

fun[i_Integer] := sow@(Print["CALLED"]; Table[None, {i}]);

Notice the use of Module instead of Block (as suggested by @WReach in the comments): This prevents the local time and out variables from colliding with like-named variables used inside the argument of sow, which would otherwise give strange results. Generally, it is better to always use Module, unless you have a very specific reason that you need Block.

Now, the functions work as intended:

reap[fun[2]]

During evaluation of CALLED
(* {{None, None}, {{"Print[CALLED]; Table[None, {i}]" -> 0.000075}}} *)
Lukas Lang
  • 33,963
  • 1
  • 51
  • 97
  • 1
    +1. In addition, I would advise the OP to replace Block with Module in the new definition. Otherwise, unexpected results could occur should any expression in the evaluation chain for y happen to use the symbols time or out. – WReach Oct 18 '19 at 15:01
  • @WReach Thanks for pointing that out, I've updated the code & added a note. (I did think of it at one point, but for some strange reason I decided that it wasn't an issue here - so thanks again for catching that :) ) – Lukas Lang Oct 18 '19 at 19:32
  • Ok, thanks, that was the missing piece. Actually, I think I've built a pretty good profiler based on this. – István Zachar Oct 19 '19 at 13:43