Function Evaluation Question from a Noob

Hello all,
I have been studying along and searched here in scsynth but cannot find the answer to this function evaluation problem. Below is a scale that I am trying to do a random walk through. If I evaluate the function individually things work well. But if I try to put this function into a do loop or a while loop or a for loop I can’t get it to work. The answer is probably simple. Can anyone help?
P.S. I realize there are probably many sophisticated ways to do this in SuperCollider but this is where I am at now.

// Just trying to get indices from the array in following example
(
~eflat = [51, 53, 55, 56, 58, 60, 62, 63, 65, 67, 68, 70, 72, 74, 75];

~start = { ~eflat.size.rand };
~start.value;

~midPt = { (~eflat.size/2).floor };
~midPt.value;

~checkIndex = { |x| if ((x < 0) || (x > (~eflat.size-1)), {~midPt.value}, {x})}; // bounds check
~checkIndex.(-1);
~checkIndex.(15);

~currIndx = 0;

~randomWalk = {
	if (0.5.coin, {~currIndx=~currIndx+1},{~currIndx=~currIndx-1});
	~currIndx = ~checkIndex.value(~currIndx);
};
)

~randomWalk.value; // <= WORKS; evaluate this repeatedly!

5.do(~randomWalk.value);     // <= DOES NOT WORK
5.do({~randomWalk.value});   // <= DOES NOT WORK
5.do({~randomWalk.value()}); // <= DOES NOT WORK
5.do{~randomWalk.value};     // <= DOES NOT WORK
5.do{~randomWalk.value()};   // <= DOES NOT WORK

What am I missing?

~randomWalk is being evaluated 5 times but nothing is being done with the return value! That is you’re neither storing them anywhere nor asking that they be posted for your inspection.

Your function sets ~currIndx 5 times more or less immediately.

evaluate this line

fork{ 5.do{~randomWalk.value; ~currIndx.postln; 1.wait} };  

and you will see the value changing.

you might also try:

5.collect{~randomWalk.value}

that will collect the returns into an array

Thank you semiquaver. Both of those work. I must say though they are not intuitive for someone at my level. Thank you again!

One suggestion I’d make here for future questions is: if you’re tempted to say “does not work,” usually some more detail would be helpful.

Most of the time, “does not work” really means “I expected it to do one thing, but it did something else.”

So it’s a good idea to a/ identify what you expected it to do and b/ describe what it did do. Without identifying these, then it’s just a completely mysterious, random, “unintuitive” phenomenon.

For example, in this case, I would guess that A/ is “I expected five values” and B/ is “it only posts 5, every time.”

This is extremely valuable troubleshooting information, not just for the people who will answer you on the forum, but also for yourself. For instance, observing that the return value of do is the same in each of your cases, you might try 5.do with a completely different function and observe that the return value is the same – which might lead to an inference that do is not about return values, but rather about something else.

Background concept

In SC (as in C, Java… etc…), a function can have two types of outcome:

  • Its direct return value.
  • Side effects.

Here’s an example of a (dumb) function that only returns a value, and doesn’t affect anything else in the system:

(
f = { |i, divisor = 20|
	i / divisor
};
)

f.(3)
-> 0.15

f.(5);
-> 0.25

5.do { |i| f.value(i); };
-> 5

5.collect { |i| f.value(i); };
-> [ 0.0, 0.05, 0.1, 0.15, 0.2 ]

We could call this a “pure function” – the only thing that matters is the output.

A little tweak gives the function a side effect:

(
z = Slider(nil, Rect(800, 200, 200, 50)).front;

f = { |i, divisor = 20|
	z.value = i / divisor;
};
)

f.(3);
-> a Slider

f.(5);
-> a Slider

Now, the function’s return value is meaningless (always “a Slider”), but the slider’s position changed. The change in position is the side effect.

The rule for do vs collect is: If you are interested in the return values, use collect. If you only need the side effects, use do. Think “do something (and I’ll see it later)” vs “collect results, and give me all of them.”

Using the slider example:

5.do { |i| f.value(i) };

You see only one screen update – but, the slider is left at a nonzero position. do starts counting at zero, but nonzero slider means that i could not have remained at zero – hence the loop must have actually run to completion, just doing the updates too fast for you to see.

If you defined A = “I want to see a slider step through five positions” and B = “it jumped to the end,” then that would tell you that the problem is not that the loop didn’t run.

In the random walk case, you have both side effects (changing the value of ~currIndx) and a return value (output ~currIndx).

~randomWalk.value; // <= WORKS; evaluate this repeatedly!

This is printing the function’s return value.

Because you’re using the return value (and the side effect is incidental), then you want collect. (do throws away the return values… but you wanted the return values… so do isn’t the right choice here.)

hjh

This may work:

~randomWalk ! 5


!

  • for class Object , duplicates the object
  • for class Function , evaluates the function
  • return each iteration in an array

Also used in SynthDefs for multi-channel expansion (e.g. creating stero output with a single oscillator)