Friday, November 21, 2014

Beware of Lazy Evaluation

A funny thing happened to me when I was presenting Bacon.js at the great Oredev conference a few weeks ago. You could say my own dogs bit me. What happened was that I used map to get the current value of a text field to be associated with a Bacon event.
The thing is, methods such as map and the combine use lazy evaluation to avoid evaluating values that aren't actually needed. This can be generally considered a Good Thing, but it has it's pitfalls.
If you pass a function that referentially transparent, you'll be fine. This means that your function should return the same value regardless of when it's called.
On the other hand, if you pass a function that returns a value depending on time, you may have problems. Consider a property contents that's derived from events like below.
var items = clicks.map(getCurrentValueFromUI).toProperty()
var submittedItems = items.sampledBy(submitClick)
Now the submittedItems stream will produce the current value of the items property when an event occurs in the submitClick stream. Or so you'd think. In fact, the value of submittedItems is evaluated at the time of the event in the submitClick stream, which means that it will actually produce the value of getCurrentValueFromUI at that time, instead of at the time of the originalclick event.
To force evaluation at the time of original event, you can just use flatMap instead of map. As in here.
var items = clicks.flatMap(getCurrentValueFromUI).toProperty()
So there I was, on the stage, looking stupid because of lazy evaluation. 
Now, looking stupid in places is what I do every day but, if it lazy evaluation fools me (the guy who wrote it), maybe it's not reasonable to expect new Bacon.js users to do well with lazy eval.
So, I opened an issue on Github where I kinda suggest that we should remove lazy evaluation in Bacon.js 0.8. What do you think?
Oh, and should I go to Minsk to the Rolling Scopes conference next year?

Wednesday, November 12, 2014

Bacon Blog is Back!

Been a while since my last post on this blog. And it's not that stuff hasn't been happening around Bacon.js. It's more that I haven't had time to write and/or have kept the bar a bit high. But now it's time for a change.

I'm gonna throw a post here weekly from now on, with short posts that take less than 30 minutes to write. Let's see what happens.

I just made 2 releases of Bacon.js today. And in total, there's been 32 minor releases since 0.7.0 that my previous post covered. A lot of bugs have been fixed and performance improvements have been made. So if you're for some reason using Bacon.js version < 0.7.32, you should update! In case you're interested in a detailed list of changes, have a look at RELEASE-NOTES.md!

Anyway, let me briefly introduce the API changes since 0.7.0.

Error handling improvements (#310)

Added Bacon.retry for retrying a background request a few times before giving up and producing an Error event. Here's @mileskin's original example with JQuery Ajax.
Bacon.retry
  source: -> Bacon.fromPromise($.ajax(...))
  retries: 4
  interval: ({retriesDone}) -> (1 + retriesDone) * 5000
  isRetryable: (response) -> response.status isnt 401
Also added flatMapError for flatMapping errors.

flatMapWithConcurrencyLimit, flatMapConcat, bufferingThrottle, holdWhen (#324)

The bufferingThrottle method allows you to throttle events with a buffer. So, when a regular throttle discards excess events, this one will buffer them.

The flatMapWithConcurrencyLimit method is a variant of flatMap that limits the number of concurrently active child streams, so that you can, for instance limit the number of concurrent AJAX requests to a certain maximum by automatically queuing the excess requests.

The flatMapConcat method is like the previous, but with concurrency limit of 1.

The holdWhen method can be used like a valve, to buffer events when a Property holds the value true, and release all buffered events when the value changes to false.

Support "on/off" events in Bacon.fromEventTarget (#461)

Some libs use the "on" and "off" methods for registering/removing event listeners. Now it's easy to integrate into those libs too. So, for instance, if you use CodeMirror and want a stream of code changes in the editor, you can now

    Bacon.fromEventTarget(codeMirror, "change")

That's it for now. Will come back next week. Suggestions for topics?