You’ve seen streams that represent a stream of events. Now we’ll look at the other type used in FRP—cell—representing a value that changes over time.
Figure 2.3 shows a trivial example. A GUI label shows the current value of the text in the text field. Change the text, and the label’s text will also change.
A cell is a nice fit for a GUI label widget.
The SLabel in SWidgets gives a visible rep-
resentation of a Cell<String> so that the screen representation is always kept up to date with the changing cell value. Figure 2.4 shows the conceptual view of plugging the text field’s output text into the SLabel, and listing 2.2 gives the code.
NOTE Cells don’t initiate state changes like streams do, so we’re drawing cells with a small arrow head in the middle of the line. This is to indicate that they’re passive, while streams are the active agency in FRP. We think this idea is important.
Figure 2.3 A label that always shows the current text of the text field
msg text Ibl
STextField SLable
Figure 2.4 Conceptual view of the label example: STextField exports its current text, and SLabel imports it.
35 The Cell type: a value that changes over time
import javax.swing.*;
import java.awt.FlowLayout;
import swidgets.*;
import nz.sodium.*;
public class label {
public static void main(String[] args) { JFrame frame = new JFrame("label");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new FlowLayout());
STextField msg = new STextField("Hello");
SLabel lbl = new SLabel(msg.text);
frame.add(msg);
frame.add(lbl);
frame.setSize(400, 160);
frame.setVisible(true);
} }
Check it out with git if you haven’t done so already, and run it like this:
git clone https://github.com/SodiumFRP/sodium cd sodium/book/swidgets/java
mvn test -Plabel or ant label
DEFINITION Cell—A container for a value that changes over time. Some FRP systems call it a behavior, property, or signal.
NOTE The term signal is mostly used in systems that don’t distinguish between stream and cell.
A stream contains events that fire at discrete times. A stream event only has a value instantaneously when it fires, but a cell always has a value that can be sampled at any time. In FRP, cells model state, while streams model state changes.
Listing 2.2 label example: a label showing text field’s text
What? Cell is a mutable variable?!
When you learn functional programming, it’s drummed into you that you shouldn’t use non-local mutable variables. Mutable variables break compositionality, a property that’s highly prized in functional programming. We agree completely.
Yet an FRP cell is modified in place, making it technically a non-local mutable variable.
What’s going on here? This paradox is resolved as follows.
State mutation is evil—in the technical sense of the word, meaning it breaks compo- sitionality. Event handling is inherently stateful, so it’s similarly touched by the mark of Cain. FRP can’t change this unavoidable fact.
In horror stories, demons can never be killed; they can only be banished. This is a good thing, because it means you can always make more money off a sequel.
Here are some things that may make sense modeled as a cell:
■ The position of the mouse cursor in an application window
■ The position of a spaceship in a video game
■ The current state of a polygon you’re editing
■ Vehicle speed, vehicle odometer, or current GPS position
■ Time
■ Whether the Wi-Fi network is online or offline
■ Signal strength
■ Temperature 2.5.1 Why Stream and Cell?
The clearfield example changes the contents of an STextField, and the label exam- ple changes an SLabel. In both cases, you pass something in to the constructor to cause those changes to occur. Compare these lines:
STextField text = new STextField(sClearIt, "Hello");
SLabel lbl = new SLabel(msg.text);
In the first line, you pass a stream; and in the second, you pass a cell. Why the difference?
■ In clearfield, both the user and the program can change the STextField’s text. Should the text field follow what the program says, or what the user says? It needs to change according to events from both sources. A stream, representing (continued)
In a similar way, FRP takes the evil of event handling, divides it into little pieces, and then banishes each piece into an evil-proof box called a stream or a cell. The evil is then divided so it can’t conspire against you, and it’s contained, rendering it good on the outside.
By good we mean it has been transformed into something that obeys the rules of func- tional programming. Although the value held inside is mutable, the container itself—
Stream or Cell—is an immutable value, so it can be used in referentially transparent functions.
It may take a little while to get your head around this, but it’s an important point, so please make sure you understand it. The immutability of the Stream and Cell classes means your FRP code is compositional. Now the techniques of functional programming are available to you, and that’s where the true benefits of FRP come from.
Note that the evil is contained only as long as Pandora doesn’t come along and open the evil-proof box. This is why it’s vital that you don’t break the rules: the functions you pass to FRP primitives must be referentially transparent. We’ll say it one more time just to be clear: cheats never prosper.
clearfield example label example
37 The Cell type: a value that changes over time
discrete events, is a better fit, because you want to model the ability to make dis- crete changes to the text field’s current text string.
■ In label, the label’s text is completely controlled by the program, and its dis- play represents a string value that can change. Cell fits this better.
2.5.2 The constant primitive: a cell with a constant value
Cells usually change over time, but it’s also possible to construct them with a constant value:
Cell<Integer> dozen = new Cell<>(12);
There’s no way to modify a cell after it’s created, so its value is guaranteed to be con- stant forever.
2.5.3 Mapping cells
We can illustrate map on cells by reversing the string in the previous example. See fig- ure 2.5.
Figure 2.6 shows the conceptual view again. (If you’re viewing this in color, we’ll show operations that output cells in blue.) Listing 2.3 shows the code.
Figure 2.6 The conceptual review of the reverse example
import javax.swing.*;
import java.awt.FlowLayout;
import swidgets.*;
import nz.sodium.*;
public class reverse {
public static void main(String[] args) { JFrame frame = new JFrame("reverse");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new FlowLayout());
STextField msg = new STextField("Hello");
Cell<String> reversed = msg.text.map(t ->
new StringBuilder(t).reverse().toString());
SLabel lbl = new SLabel(reversed);
frame.add(msg);
frame.add(lbl);
Listing 2.3 Using map to reverse the text
Figure 2.5 Using map to reverse the text in a cell
text msg
reversed
reverse(t) Ibl
STextField
t
SLabel
frame.setSize(400, 160);
frame.setVisible(true);
} }
Check it out with git if you haven’t done so already, and run it like this:
git clone https://github.com/SodiumFRP/sodium cd sodium/book/swidgets/java
mvn test -Preverse or ant reverse