~ / track F / concurrency models

Functional reactive programming

Advanced

Functional Reactive Programming (FRP) models a program as a graph of values that change over time — signals, streams, observables — connected by pure transformations. You declare the relationships: "the total displayed on screen is the sum of the items in the cart." When inputs change, the graph re-evaluates the dependents. UI frameworks like RxJS, ReactiveX, Re-frame, and the React rendering model are all FRP-flavored.

The two basic ideas

  1. Time-varying values. A Signal[T] or Stream[T] represents a value of type T that changes over time. Instead of repeatedly polling, your code subscribes to changes.
  2. Pure transformations between them. map, filter, combine, scan — the same vocabulary you use on collections, lifted to time. Connections in the graph are pure; effects happen at the leaves.

Streams in Clojure: a sketch

core.async channels are a small step away from FRP — you can build stream-like pipelines on top. Here we'll simulate a stream of clicks with a vector, just to feel the shape:

loading sci
press ⌘/Ctrl-↵ or click ▶ run to evaluate

In an actual UI, clicks would be a live event stream and counts would be a derived signal that updates whenever a click arrives. The structure of the code is the same; the runtime swaps "reduce a vector" for "incrementally update on each event."

Glitch-free dependency tracking

The hard part FRP libraries solve for you: when two signals depend on the same upstream signal, and that upstream changes, you must see a consistent snapshot downstream. Naive subscriptions can show intermediate states ("the sum was updated but the average hasn't recomputed yet").

A real FRP library batches recomputation in dependency order so observers never see inconsistent state. This is "glitch-free" reactivity.

Re-frame: FRP for the Clojure UI world

re-frame is the canonical Clojure FRP-flavored UI framework. The model:

  • App-db: a single atom holding all UI state.
  • Subscriptions: pure derivations from app-db. Components re-render when their subscriptions' values change.
  • Events: messages that describe state transitions; handlers update app-db.
  • Effects: side effects (HTTP, local storage) declared as data.

The flow is one-way: events → app-db → subscriptions → components → events. That's FRP packaged for production UIs.

When FRP fits

  • UI code with many interdependent derived values. A naive "subscribe and update everything" approach explodes; FRP's dependency graph keeps it tractable.
  • Stream processing pipelines. Real-time analytics, event-driven middleware, sensor data fusion.
  • Anywhere "the consumer should react when the producer changes."

When the data is mostly static or single-shot, plain functions are simpler.

Check yourself

? quiz

What is the value of describing dependent computations as a graph instead of as imperative subscriptions and callbacks?

Exercise

Sketch (in code or prose) a tiny shopping-cart reactive graph with these signals:

  • items — vector of {:price ... :qty ...}
  • subtotal — sum of (* price qty) across items
  • tax-rate — 0.10
  • totalsubtotal * (1 + tax-rate)

How does the runtime know to update total when items changes, but not when an unrelated piece of state changes?

 status: new