Shouldn't Events be (directly) composable?

Actually a simple “record override” like copy.putAll is not a terribly useful notion of Event composition because there’s no way to do any lookups in the “prior” environment, i.e. no way to get a Pkey equivalent.

So instead one can use proto chaining, and a “recursive resolve”

+ Event {

	/* "record override" not very useful
	<> { arg anEvent;
		^anEvent.copy.putAll(this)
	}*/
	<> { arg anEvent;
		this.proto_(anEvent); ^this;
	}

	atRec { arg key;
		var val = this.at(key), env = this;
		while ({val.isKindOf(AbstractFunction)},
               {val = env.proto.use { val.value }; env = env.proto; })
		^val
	}
}

At least for some basic recursive lookup this seems to work, i.e.

e = (freq: {~freq * 2}) <> (freq: 200, bar: 24);
d = (freq: {~freq + ~bar}) <> e;

e.atRec(\freq) // 400
d.atRec(\freq) // 424

I’m guessing there is a way to break this since the ASTs that BinaryOpFunction builds aren’t as comprehensive as Patterns.

Also, there’s no notion of sequencing here, just composition, and one needs to decide when to resolve the values, i.e. before playing:

+ Event {
	resolve {
		var ev = this.copy;
		this.keysDo { |k| ev[k] = this.atRec(k) };
		^ev
	}
}

e.resolve // ( 'freq': 400 )
d.resolve // ( 'freq': 424 )

I found out where this approach breaks, namely

~freq = 11
e = (freq: r { loop { yield (~freq * 2) } }) <> (freq: 200, bar: 24)
e.resolve // ( 'freq': 22 )

Routines are AbstractionFunction too, but they don’t look up in the use environment for some reason, i.e.

~foo = 12
f = {~foo + 2}
(foo: 44).use { f.value } // -> 46
// but
r = r { loop { yield (~foo + 2) } }
(foo: 44).use { r.value } // -> 14

I’m not seeing a way to fix this right now. Adding extra nesting like yield ({~freq} * 2) has the problem of decoupling the proto advance/recursion from the value recursion. In Patterns you don’t really have this problem because the association is not externally held, i.e. each pattern holds enumerable refs to those patterns it depends on for data.

Also, generally

f = {42}
g = f + 3
g.value // -> 45; In contrast
h = r { loop { (f + 3).yield } } // needs a double "resolve"
h.value // -> a BinaryOpFunction
h.value.value // 45

Now I get to appreciate why embedInStream “recurses” as a function but “returns values” with yield so it can pop that value out of an arbitrary number of nestlings that are not known in advance… And yeah, there’s a way to fix this, but it’s not quite transparent to the routine (writer), i.e.

f = {42}
h = r { loop { (f + 3).value.yield } } 
h.value // 45

The problem with applying this to the Even chaining here is that Routine needs to do the environment change, and it can’t access it in my setup.