If the end goal is to change the embedder’s output stream, then we’re discussing a couple ways to do that:
-
The “classical mechanics cause and effect” way: change the embedder’s behavior by doing something to the embedder;
-
Or the “quantum mechanics spooky action at a distance” way: change the embedder’s behavior by changing a separate variable.
One of these is self-sufficient (to use a
, you need only a
), clear, and easy to troubleshoot; the other depends on potentially fragile relationships (to use a
, you need a
and b
– this goes against the preference in object-oriented programming for clean encapsulation).
The “spooky action” way might seem cool and convenient, but experience tells me that you’ll start seeing bugs as things get more complex. To me, it looks like an anti-pattern and I would not recommend it. You’re free to try, of course, but you’re likely to run into trouble later.
This is not to mention that your “pass a function” solution might not be doing exactly what you thought:
b = Pseq([10, 11, 12], 1);
a = RoutineEmbedder({ b });
a.next; // 10, OK
b = Pseq([0, 1, 2], 1);
a.next; // 11 -- is that what you wanted?
a.next; // 12 -- is that what you wanted?
a.next; // 0 -- *now* it switches
The func.value
happens only when embedInStream
releases control, which happens only at the end of the pattern currently being embedded. But you had said that you wanted b=Pseq([0, 0, 0, 0], 1); a.next;
to switch immediately. Your solution is not guaranteed to do that.
You can make the “classical mechanics” way look friendlier by defining a setter method instead of init
:
// Class definition:
RoutineEmbedder {
var <source, routine;
*new { arg source;
^super.new.source_(source)
}
source_ { arg argSource;
source = argSource;
routine = Routine { |inval|
loop {
inval = source.embedInStream(inval)
}
};
}
next { |inval|
^routine.next(inval);
}
}
// Usage:
a = RoutineEmbedder(Pseq([10, 11, 12], 1));
a.next; // 10, OK
a.source = Pseq([0, 1, 2], 1);
a.next; // 0, OK!
Also note the handling of inval
– maybe you don’t need it yet, but you will, if you want to stream a Pbind.
Alternately, you could use an environment that broadcasts changes to registered client objects (the Observer OOP design pattern):
// Class definition:
RoutineEmbedder {
var <source, routine;
var <>key;
*new { arg source;
^super.new.source_(source)
}
source_ { arg argSource;
source = argSource;
routine = Routine { |inval|
loop {
inval = source.embedInStream(inval)
}
};
}
next { |inval|
^routine.next(inval);
}
update { |obj, what ... args|
// message would be:
// envir.changed(\source, envVarName)
// passing in obj = envir, what = \source, args = [envVarName]
if(what == \source and: { args[0] == key }) {
this.source = obj[args[0]];
};
}
}
// Usage:
(
e = EnvironmentRedirect(Environment.new)
.dispatch_({ |varname, value|
e.changed(\source, varname)
});
a = RoutineEmbedder.new.key_(\b);
e.addDependant(a);
)
e.push;
~b = Pseq([10, 11, 12], 1);
a.next; // 10, 11, 12...
~b = Pseq([0, 1, 2], 1);
a.next; // 0, 1, 2...
// btw when you're finished with a:
e.removeDependant(a);
No – you haven’t passed b as a variable. You’ve passed a function that refers to b. If you were truly passing b as a variable, then the extra .value
would be unnecessary.
hjh