1
$\begingroup$

Synopsis

Below serves as a skeleton for a function that will track number of occurrences of a particular notebook event. The nth event will trigger a function call. After which the counter symbol must be reset eval=0 so that function can be called again on next nth event.

The event will automatically increment counter eval which is wrapped in an observer function Experimental`ValueFunction[eval] :=f which will perform a function call if a condition is met and reset counter when eval == n.

Problem With Code

Well it doesn't quite work. It throws an occassioanal error $RecursionLimit... and fails to reset eval = 0. Please share your insight.

Exhibit 1: Buggy Code

Quiet@Remove@"Global`*";
n = 3;
resetOnNth[n_Integer : 6] := Block[{},
   eval = 0;
   Needs["Experimental`"];
   Experimental`ValueFunction[eval] := 
    If[eval === n, eval = 0, Echo[eval, "eval \[Rule]"]];
   Return@eval
   ];

resetOnNth[n];

(* event simulator: 2 loops of 3 events *)
Do[++eval, n, 2];

Exhibit 2: Expected Output - If Good

(Edited: was {0,1,2,0,1,2})

>> eval ->  1
>> eval ->  2
>> eval ->  3
>> eval ->  1
>> eval ->  2
>> eval ->  3

Exhibit 3: Actual Output - Bad

>> eval ->  6
>> eval ->  0
>> eval ->  0
>> eval ->  1
>> eval ->  2

$RecursionLimit::reclim2: Recursion depth of 1024 exceeded during evaluation of eval===3.

>> eval ->  4
>> eval ->  5
$\endgroup$
6
  • $\begingroup$ @DanielHuber do you think you may have some insight on this? $\endgroup$ Jan 25, 2022 at 18:52
  • 1
    $\begingroup$ At first sight, this looks a bit of an XY problem. Can you perhaps elaborate more on the actual problem you are trying to solve? Can you not – instead of ++eval and complications with Experimental`ValueFunction – simply use something like incrementEval[], where incrementEval[] := If[eval === n, eval = 0, Echo[eval, "eval \[Rule]"]; eval++];? $\endgroup$
    – Domen
    Jan 25, 2022 at 19:18
  • 1
    $\begingroup$ If you reset "eval" to zero inside ""ExperimentalValueFunction"" , you are again triggering "ExperimentalValueFunction" again, what leads to an infinite recursion. $\endgroup$ Jan 25, 2022 at 19:22
  • 1
    $\begingroup$ Avoid this ValueFunction nonsense and use an UpValue: eval /: PreIncrement[eval] /; eval == 3 := (eval = 0) $\endgroup$
    – Jason B.
    Jan 25, 2022 at 19:42
  • $\begingroup$ Using the hint from Jason you woul write: ..eval /: PreIncrement[eval] /; eval == 3 := (eval = 0) resetOnNth[n_Integer : 6] := Block[{}, eval = 0; Needs["Experimental"]; ExperimentalValueFunction[eval] := If[eval =!= n, Echo[eval, "eval \[Rule]"]]; Return@eval];.. $\endgroup$ Jan 25, 2022 at 21:08

2 Answers 2

3
$\begingroup$

As ordered :)

Quiet@Remove@"Global`*";
n = 3;
eval /: PreIncrement[eval] /; eval == n := (eval = 0);

resetOnNth[n_Integer : 6] := 
  Block[{}, eval = 0; 
   Experimental`ValueFunction[eval] := 
    If[Mod[eval, n] =!= 0, Echo[eval, "eval \[Rule]"]]; Return@eval];

resetOnNth[n];

Do[++eval, n, 2];

enter image description here

$\endgroup$
5
  • $\begingroup$ Thank you for sharing your insight and expertise with me You probably saved me at least a few hair-pulling days of intense research and reading and yelling that infamous 4-letter word out loud in my office every time i hit a wall of confusion or misunderstanding. $\endgroup$ Jan 25, 2022 at 22:51
  • $\begingroup$ @DaniellHuber ive been studying your solution. i had no idea what this was: eval /: PreIncrement[eval] /; eval == n := (eval = 0); but after a quick search in docs reveals it as a TagSetDelayed which I have never done before. I intend to to learn more about it, thank you again for your elegant solution. $\endgroup$ Jan 25, 2022 at 23:03
  • 1
    $\begingroup$ PreIncrement[eval] /; eval == n means the pattern only matches if eval==n. eval /: means that the assignment should be stored as upvalue under the name "eval" and not "Preincrement" what would give an error, because "Preincrement " is protected $\endgroup$ Jan 26, 2022 at 8:57
  • $\begingroup$ after a closer i look j did notice that your solution was still slightly off. the . order of the output is reversed and one short {2,1,2,1 }. should be {1,2,3,1,2,3}. im not sure if i know how to correct it but ill give a good try maybe by tomorrow night. $\endgroup$ Jan 26, 2022 at 10:32
  • $\begingroup$ Please view answer I posted. I got about as far as I could get. My refactored solution is almost there. $\endgroup$ Jan 26, 2022 at 16:44
0
$\begingroup$

My Almost There Solution

What Changed and More on Final Goal

Based on very valuable near-solutions by Daniel Huber and Jason B. Function name was resetOnNth. Changed to observer to better reflect its purpose of only initializing Global functions and symbols that observe Kernel evaluations and react accordingly. Eventually the Do loop will be replaced by $Pre = on Kernel evaluation eval++. The purpose of Echo@eval is to monitor the performance of Kernel counter eval during development. When we get this running smoothly those will no longer be needed.

Changed PreIncrement to Increment for no real reason. Also changed Mod[...] to eval < n as it seems to work better at least for now. Added Print[...] as a placeholder for intended function NotebookAutoBackup to *Upvalue so that it occurs when eval==n which is every nth Kernel evaluation. And finally changed n to $n to make certain there is no shadowing and now 3 running loops to better observe the pattern eval counter makes. More is explained in the code as comments.

I wish to add that my heart is not set on any of these changes. I am open to anything that might fix it.

Exhibit 1: Refactored - Almost Works

Quiet@Remove@"Global`*";
 ClearSystemCache[];

(* everything moved inside observer *)
(* it only needs to evaluate once *)
(* because it only initializes globals *)
observer[n_Integer : 6] := Block[{},
   eval = 0;
   eval /: 
    Increment[eval] /; 
     eval == n := (Print@
      Row[{"{n,eval} \[Rule] ", {n, eval}, 
        ": placeholder for NotebookAutoBackup[]"}]; eval = 0);
   Needs["Experimental`"];
   Experimental`ValueFunction[eval] := 
    If[eval < n, Echo[eval, "eval \[Rule]"]];
   eval
   ];

$n = 3;
observer[$n];
Column[{Row[{"$n: ", $n}], Row[{"eval: ", eval}]}]

(* simulate Kernel evaluations *)
Do[eval++, $n 3];

Exhibit 2: Intended Output

>> eval ->  1
>> eval ->  2
{n,eval} -> {3,3}: placeholder for NotebookAutoBackup[]

>> eval ->  1
>> eval ->  2
{n,eval} -> {3,3}: placeholder for NotebookAutoBackup[]

>> eval ->  1
>> eval ->  2
{n,eval} -> {3,3}: placeholder for NotebookAutoBackup[]

Exhibit 3: Actual Output

Notice how the counter eval now runs in the correct direction and even performs the most important function when eval == n. In fact it would run pretty decent now. Problem is that is not good enough for publishing content at Mathematica Function Repository. It has a quirky start and performs intended function on 4th evaluation not 3rd. The end loop doesnt complete but that doesn't concern much because I like the pattern it takes. Run the actual code to see more inspectors (not shown below) inserted into it.

>> eval ->  1

>> eval ->  0
>> eval ->  1
>> eval ->  2
{n,eval} -> {3,3}: placeholder for NotebookAutoBackup[]

>> eval ->  0
>> eval ->  1
>> eval ->  2
{n,eval} -> {3,3}: placeholder for NotebookAutoBackup[]

>> eval ->  0
$\endgroup$
6
  • 1
    $\begingroup$ So the goal is to execute a given kernel command on every third evaluation? That should be easy enough to do with $Pre without using this ValueFunction $\endgroup$
    – Jason B.
    Jan 26, 2022 at 16:57
  • 1
    $\begingroup$ Something like Module[{counter = 0}, $Pre = (If[Divisible[++counter, 3], Print["we are saving now"]]; #) &] would do the trick, if that is what you are going for. $\endgroup$
    – Jason B.
    Jan 26, 2022 at 17:04
  • 1
    $\begingroup$ Remove the first eval = 0; in "observer". And iInitialize eval to 1. $\endgroup$ Jan 26, 2022 at 17:13
  • $\begingroup$ @JasonB. well it needs to evaluate for any nth positive integer selected by end-user but i <3 where youre going with this. Does this look like it would work? AutoBackup[n_Integer?PositiveQ]:=Module[{counter = 0}, $Pre = (If[Divisible[++counter,n], NotebookBackup[]]; #) &] $\endgroup$ Jan 26, 2022 at 17:33
  • 1
    $\begingroup$ That looks right to me $\endgroup$
    – Jason B.
    Jan 26, 2022 at 19:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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