Now we’ll return to RxJS. The final state-keeping method we need to cover is combineLatest, which allows you to combine the current values of two Cell-like input observables. It works as expected when its inputs are BehaviorSubjects. When used like this, it corresponds to lift in Sodium.
Figure 6.4 shows the trivial but classic use case of FRP lift: adding two numbers represented as cells. Listings 6.8 and 6.9 give the code. Some browsers will retain the content of the text fields if you reload. You’ll see that
everything works in a properly cell-like manner, so the sum starts off correct.
<html><head>
<title>Add two numbers</title>
<style>
#a { width: 80px; } #b { width: 80px; } </style>
<script src="rx.all.min.js"></script>
<script src="add.js"></script>
</head>
<body onload="init()">
<input id="a" type="text"/> + <input id="b" type="text"/> = <span id="c"/>
</body>
</html>
function currentTextOf(input) { var sKeyPresses = Rx.Observable.fromEvent(input, 'keyup'),
text = new Rx.BehaviorSubject(input.value);
sKeyPresses.map(function (e) { return input.value; }).subscribe(text);
return text;
}
function init() {
var a = currentTextOf(document.getElementById('a')) .map(function(text) { return parseInt(text); }),
Listing 6.8 add.html: Adding two numbers together
Listing 6.9 add.js
Figure 6.4 Adding two numbers together
BehaviorSubject of the current text of an input field
b = currentTextOf(document.getElementById('b')) .map(function(text) { return parseInt(text); }), cSpan = document.getElementById('c');
var c = a.combineLatest(b, function(aa, bb) { return aa + bb; });
c.subscribe(function(cc) { cSpan.innerHTML = cc; });
}
To run this example, point your browser at sodium/book/web/add.html.
6.4.1 Glitches in combineLatest
We said in chapter 1 that because Rx isn’t based on denotational semantics, it isn’t truly compositional, and this is one of the requirements of FRP. One way in which this manifests is the area of glitches. A true FRP system should be glitch-free.
A glitch is defined as a visible output that isn’t consistent with the relationships defined by the FRP code. To demonstrate a glitch, you need simultaneous events.
Recall that two simultaneous events must originate in a single stream. This is the sort of thing that doesn’t come up in simple programs but starts to become a problem as they get more complex.
The quickest way to illustrate is with a contrived example. Suppose you have two simultaneous events—ones and hundreds. You’ll feed the numbers 1 and 2 into ones. hundreds is ones multiplied by 100, and sum is the sum of ones and hundreds. In a perfect, glitch-free world, figure 6.5 shows what you would like to see visible in sum.
If you code this in RxJS (see listing 6.10) and then load it in the browser, you get these alerts:
■ 101
■ 102
■ 202
The number 102 is referred to as a glitch because ones has the value of 2 at this point, so to be consistent, hundreds should be 200. 102 isn’t consistent with the relationships described in the code. In an FRP system, no inconsistent state should be observable.
<html><head>
<title>Glitch</title>
<script src="rx.all.min.js"></script>
</head>
<body>
<script type="text/javascript">
var ones = Rx.Observable.range(1, 2);
var hundreds = ones.map(function(x) { return x * 100; });
Listing 6.10 How RxJS handles simultaneous events
ones
hundreds
time
1 2
100 200
sum 101 202
Figure 6.5 The sum of simultaneous events ones and hundreds
123 The latest of two observables with combineLatest
var sum = ones.combineLatest(hundreds, function(o, h) { return o + h; });
sum.subscribe(function(s) { alert(s); });
</script>
</body>
To run this example, point your browser at sodium/book/web/glitch.html.
Listing 6.11 gives the Sodium equivalent. Sodium doesn’t give glitches, so its out- put is as you’d like:
101 202
import nz.sodium.*;
public class glitch {
public static void main(String[] args) { CellSink<Integer> ones = new CellSink<>(1);
Cell<Integer> hundreds = ones.map(o -> o * 100);
Cell<Integer> sum = ones.lift(hundreds, (o, h) -> o + h);
Listener l = sum.listen(s -> System.out.println(s));
ones.send(2);
l.unlisten();
} }
When evaluating a system, you need to understand how it deals with simultaneous events and decide how important that is to your project. Once you’ve invested in a sys- tem for your project, you’re stuck with its glitch behavior. It can’t be fixed later.
What plagues does RxJS banish? See table 6.1.
Notes:
■ Threading issues—JavaScript doesn’t have threads, so there are obviously no threading issues. The Rx system in other languages has imperfect ways of dealing with threading issues, but they’re an improvement over traditional ways Listing 6.11 How Sodium handles simultaneous events
Table 6.1 What plagues does RxJS banish?
Plague Banished?
Unpredictable order No
Missed first event Yes
Messy state Yes
Threading issues Partially (see the following notes)
Leaking callbacks Yes
Accidental recursion No (see the following notes)
of doing things. This isn’t as good as things could be. FRP systems can eliminate threading issues altogether, as Sodium demonstrates.
■ Accidental recursion—Rx allows a subscriber’s handler code to push events into an observable, which can lead to accidental recursion. Having said that, if you have the self-control not to do this, you should avoid this problem. Sodium strictly enforces a ban on doing this, as we’ll explain in chapter 8. This is why it can completely eliminate this plague.
6.4.2 merge isn’t compositional
A second, similar problem that RxJS and some other system have is that merge doesn’t deal consistently with simultaneous events. This is exactly the same issue we described in section 5.3. This lack of compositionality creates more and more practical difficul- ties as program size increases, and as we’ve explained, these will eventually com- pound. You need to consider these issues when selecting an FRP system for your project.