3
$\begingroup$

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.

$\endgroup$
3
$\begingroup$

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}}} *)
$\endgroup$
  • 1
    $\begingroup$ +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. $\endgroup$ – WReach Oct 18 at 15:01
  • $\begingroup$ @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 :) ) $\endgroup$ – Lukas Lang Oct 18 at 19:32
  • $\begingroup$ Ok, thanks, that was the missing piece. Actually, I think I've built a pretty good profiler based on this. $\endgroup$ – István Zachar Oct 19 at 13:43

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.