Shouldn't Events be (directly) composable?

Yes that was more/later fancy idea that would keep a reference to the “old target” event

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

Both my proposals also happens to reverse the order composition just like next in Event turns out to do compared with composeEvents; see previous towards the end where I now realized (and edited this in). But to repeat that observation here:

(zz: 1).next((zz: 2)) // -> ( 'zz': 1 )
(zz: 1).composeEvents((zz: 2)) // -> ( 'zz': 2 )
(zz: 1) ++ (zz: 2) // -> ( 'zz': 2 )

And (later discovery) so that next does not feel lonely, it also has a synonym: transformEvent:

(zz: 1).transformEvent((zz: 2)) // -> ( 'zz': 1 )

And this one wants to be a slightly more general interface, alas it’s documented on the page for nil!

(\zz -> 1).transformEvent((zz: 2)) // -> ( 'zz': 1 )
(_[\zz] = 1).transformEvent((zz: 2)) // -> ( 'zz': 1 )

And of some interest, this one actually modifies the target argument event, so not actually identical to next. The comment for in the code says

	// Pattern support
	transformEvent { arg event;
		^event.putAll(this);
	}

The association and function variants of this method also have this zero-copy semantics on the target event, i.e. they allow in-place modification.

But transformEvent is not actually being called in the classlib, so I think it was deprecated in favor of next, which is used in such (Pattern) contexts…

Obviously a <> on Events should have the arguments order what next does, not what ++ does because

(Pbind(\zz, 1) <> (zz: 2)).iter.next // -> ( 'zz': 1 )

For my own illumination, I’ve also tested these:

a = (zz: 1, od: 2)
b = (zz: 2, mo: 5)

// b gives defaults for a "with union" for uncommons
a.blend(b, 0) // -> ( 'zz': 1, 'od': 2, 'mo': 5 )
a.blend(b, 0) == a.next(b) // true

// b overrides a "with union" for uncommons
a.blend(b, 1) // -> ( 'zz': 2, 'od': 2, 'mo': 5 )
a.blend(b, 1) == (a ++ b) // true

// b gives defaults for a "with intersection"; 
// i.e. uncommons dropped "both sides"
a.blend(b, 0, false) // -> ( 'zz': 1 )

// b overrides a "with intersection" ...
a.blend(b, 1, false)  // -> ( 'zz': 2 )

But it gets a bit more interesting, as the above are not all the “combos”

// b overrides a on common keys, 
// but a gets to keep just its own uncommons
// could be called "get news only if interested in topic(s)"
a ++ a.blend(b, 1, false) // -> ( 'od': 2, 'zz': 2 )

// the dual of the above, although I don't have a good name for it
// keep common stuff from a, uncommons from b
b ++ a.blend(b, 0, false) // -> ( 'mo': 5, 'zz': 1 )

There are also some boring combinations that yield either a or b back, e.g.

a ++  (a.blend(b, 0, false)) == a
a.next(a.blend(b, 0, false)) == a

I suppose I could look through the paper in the related discussion again and find the more CS names proposed for these, but I can’t be bothered right now. composeEvents (and ++) does work exactly like “record override” as defined in that paper.

Actually I did look even in the longer paper of Cardelli and Mitchell, but those don’t have names. given, although they are obtainable by a so-called restriction applied before override. The restriction removes keys not found in a given set.

From a more practical perspective, there’s nothing too deep going on in the above. There are 4 set “combinations” of keys (union, intersection, and the two original sets of keys) deciding which key set goes into the result, combined with another decision bit of “who does the override” (or who “wins the conflict”) setting the values on the common keys. Of those 8 combinations in total, 6 are non-trivial but two give back the respective starting dicts. Alternatively explained, there a 3 orthogonal “decision bits”: whether to keep keys that exist only in a, likewise for keys that exist only in b, and who gets to set the values on the common keys. (There’s actually a 4th “bit” if one wants to consider not keeping common keys at all, but I haven’t considered that above, i.e. then there a 3 choices for the common stuff not two. And that’s making the decision “a priory” just based on keys, not values. Bracha and Lindstrom actually defined their “merge” to produce the common keys only if they agreed on values, but be undefined otherwise. Considering the values too makes the “decision space” larger still.)


To refocus this discussion on SC though, I expect that very basic constraint on a <> defined for Events to produce the same result as Pchain, at least for non-function fields, ie.

a = (foo: 2)
b = (foo: 1, bar: 0)
Pchain(a, b).iter.next == (foo: 2, bar: 0) == (a <> b)

which of course doesn’t happen at the moment because the rightmost expression is nil with the standard classlib.

Also

e = ()
p = Pbind()

e <> p // should be non-nil and return a Pchain(e, p) at least.