12
Choose a primitive
No primitive covers every case, and the essay argues none should. Answer a few questions about your data to find the primitive that fits. The matrix shows how they differ.
Bringing all of these reactive concepts into a single framework gives us an opportunity to tell a coherent story about reactive programming, promotes a better understanding about what tool is right for the job, and obviates the debate over whether any single primitive is a silver bullet.
How many values?
| Number | Axis | Cast | Cancel | Info | Control | Lossless | |
|---|---|---|---|---|---|---|---|
| Value | singular | spatial | — | no | one way | — | yes |
| Iterator | plural | spatial | unicast | yes | two way | pull | yes |
| Promise | singular | temporal | broadcast | no | one way | push | yes |
| Task | singular | temporal | unicast | yes | two way | push | yes |
| Stream | plural | temporal | unicast | yes | two way | pressure | yes |
| Signal | plural | temporal | broadcast | no | one way | push | no |
| Behavior | plural | temporal | broadcast | no | one way | poll | no |
Passing one primitive to another
Promise → Task
coerce either way; a task forks into a promise
Signal → Behavior
an observable's next() polls the latest value
Behavior → Signal
sample on a clock, push the samples
Signal → Stream
buffer pushed values so none are lost
Stream → Signal
drop pressure, broadcast each value
Iterator → Stream
Stream.from(iterable) lifts it onto the time axis
Stream → Promise
all(), join(), read(): collect into one value
- why it mattersThere is a tension between cancelability and robustness. Promises guarantee that consumers and producers can't interfere; tasks and streams cooperate, so they can cancel.
- why it mattersUse pressure to handle resource contention when every value matters. Use push (signals) or poll (behaviors) to skip states that don't.
- why it mattersThe primitives are related, so converting between them is cheap. Promises and tasks make a good channel for plural signals and behaviors.