I've been following this snake example and decided to modify it to generate new apples only in empty (i.e. non-snake) cells. However, that's introduced a cyclic dependency between Observables, since generating new apples now depends not only on the last position but on the whole snake:
// stream of last `length` positions -- snake cells
var currentSnake = currentPosition.slidingWindowBy(length);
// stream of apple positions
var apples = appleStream(currentSnake);
// length of snake
var length = apples.scan(1, function(l) { return l + 1; });
Is there a nice way to resolve the cycle?
I can imagine how this would work in a messy state machine but not with clean FRP.
The closest I can think of is coalescing apples
and length
into one stream and making that stream generate its own "currentSnake"
from currentPosition
.
applesAndLength --> currentPosition
^ ^
| /
currentSnake
I haven't thought about the implementation much, though.
Once it has been constructed, Bacon can usually handle a cyclic dependency between Observable
s. It is constructing them that's a bit tricky.
In a language like Javascript, to create a structure with a cycle in it (i.e. a doubly-linked list), you need a mutable variable. For regular objects you use a regular variable or field to do that, e.g.
var tail = { prev: null, next: null };
var head = { prev: null, next: tail };
tail.prev = head; // mutating 'tail' here!
In Bacon, we operate on Observables instead of variables and objects, so we need some kind of a mutable observable to reach the same ends. Thankfully, Bacon.Bus
is just the kind of observable we need:
var apples = new Bacon.Bus(); // plugged in later
var length = apples.scan(1, function(l) { return l + 1; });
var currentSnake = currentPosition.slidingWindowBy(length);
apples.plug(appleStream(currentSnake)); // mutating 'apples' here!
In my experience, it is preferrable to cut the cycles at EventStream
s instead of Properties
, because initial values tend to get lost otherwise; thus the reordering of apples
and length
.