This is the stuff I've been slowly working on in my spare time. I'm so glad I finally got this version released, since it's been in a kind of a 99% ready state for quite a long time, and I absolutely hate to have any "almost ready" code rotting in a codebase. Gotta ship it!
And the timing is of course perfect too, so you can consider this my
$holiday_occurring_in_late_december
present to you.Glitch-free FRP #272
The main goal of the 0.7.0 release is to make FRP with Bacon.js essentially glitch-free. And this is the most invasive change into Bacon.js internals for quite a while. Also something that I many times thought impossible to solve with the brain capacity available to me.
Let's start with the problem that I'm trying to solve here. A glitch in FRP is a temporary violation of an invariant that, with bacon.js at least, occurs when simultaneous events are involved. And just to be precise, my definition for simultaneous events is events that have the same root event. So, for instance, a stream
a
and its descendant a.map((x) -> x * 2)
produce simultaneous events by this definition, while a = Bacon.later(1)
and b = Bacon.later(1)
do not.
The exec summary of this improvement is that 0.7.0 is more reliable.
But if you're intrested in the details, read on! For instance, if you use Bacon.js 0.6.x and run this
You'll get
where the pair
[2,2]
is a glitch that occurs because c
is updated first by the changed value of a
and the the changed value ofb
. In glitch-free FRP you should get just
Since 0.4.0, Bacon.js has featured "atomic updates", but that has beed quite limited, because they only apply to a graph of Properties. The above example fails in pre-0.7.0 because of this limitation. In this example, the problem could be worked-around by making
a
a property as in
But there are more complex cases that are harder to work-around. Like if you have a complex graph of Properties that you combine using
combineWith(a, b, f)
, the pre-0.7.0 system doesn't guarantee that your combinator function f
won't be called for some invalid intermediate pairs of values when the sources a
and b
occur simultaneously.
And then there are
takeUntil
and skipUntil
. Previously the functioning of these combinators has depended on subscription order. For example, the correct functioning of a.takeUntil(b)
in case of simultaneous events from sources a
and b
has depended on that the event from b gets in first. And it did in most cases but not always. There's a new test case (look for the word "evil") that fails for pre-0.7.0 versions.
How did I solve this? Simply using a dependency-graph. Now all Observables have a list of "deps". For instance, if you run
You'll get
So now, using this new dependency information, it's possible to hold the output of any combined Observable until all of its deps are ready. And that's how it works. Well, except when you add a new subscriber while bacon.js is currently flushing events. To make that work flawlessly too, I had to shave another yak. But it's shaved now and all is well.
Observable.toString #265
As you might have spotted above, there's now a rather nice
toString
method for all Observables. The output of this method resembles very closely to the code that was used to create the Observable. For instance
will output
Also, I've added a
name
method as @rassie suggested in #273 and implemented .inspect()
as an alias to .toString()
so that Node.js will show your Observables in a nice manner in the console.
So now you can do this in Node.js.
Support for analysis/debugging/profiling tools #273
These are some experimental features that I included in 0.7.0 for preview.
I'm talking about improvements that will enable the implementation of development tools that can, for instance, visualize the event network and event flow going on. The improvements include
observable.deps()
Returns an array of dependencies that the Observable has. For instance, for
a.map(function() {})
, the deps would return [a]
. This method returns the "visible" dependencies only, skipping internal details. This method is thus suitable for visualization tools.
Internally, many combinator functions depend on other combinators to create intermediate Observables that the result will actually depend on. The
deps
method will skip these internal dependencies.observable.toString()
Returns a nice textual presentation of the stream. Covered in #265.
observable.internalDeps()
Returns the true dependencies of the observable, including the intermediate "hidden" Observables. This method is for Bacon.js internal purposes but could be useful for debugging/analysis tools as well.
observable.desc()
Returns a structured version of what
toString
returns. The structured description is an object that contains the fields context
,method
and args
. For example, for Bacon.fromArray([1,2,3]).desc()
you'd getBacon.spy(f)
Adds your function as a "spy" that will get notified on all new Observables. This will allow a visualization/analytis tool to spy on all Bacon activity. Current implementation just calls your function, but maybe, it should allow you to wrap all created Observables in your own wrapper to make spying even easier? This would enable AOFRP (aspect oriented functional reactive programming) lol.
All of this stuff is included in 0.7.0 but only as experimental features subject to change later. The problem is that
spy
alone is not enough for visualizing the event network. To observe the lifecycle and events passing through all the Observables, we need to add a way to observe without forcing a subscription. Currently, all you can do is to call subscribe
to watch all the events, but that will also prevent the stream from being disposed when it has no "real" subscribers. Nasty details to be be found in #273.
So, even though my intention was to "officially" include #273 into 0.7.0 too, it proved a bit more involved than it seemed at first, so we'll get back to it later.
Anyways, enjoy the new version of Bacon.js and please let me know what you think!
Cheers!
@raimohanska