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$

1 Answer 1

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$
3
  • 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$ Commented Oct 18, 2019 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$ Commented Oct 18, 2019 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$ Commented Oct 19, 2019 at 13:43

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.