BasicsDecCodeStory_Concurrency.html
copyright © James Fawcett
Revised: 05/11/2026
11.0 Prologue
Imperative concurrency requires the programmer to manually coordinate access to shared
mutable state using locks, mutexes, and condition variables - a recipe for
deadlocks, race conditions, and subtle ordering bugs. Declarative approaches attack
the root cause: either eliminate shared mutable state entirely (actors, FRP) or make
concurrent state changes composable and atomic (STM). The result is concurrent code
that is easier to reason about and test.
11.1 Software Transactional Memory
Software transactional memory (STM) borrows the database concept of
transactions for in-memory state. A transaction reads and writes shared variables
(TVars in Haskell); if no conflict is detected at commit time, the changes
are applied atomically. If a conflict occurs, the transaction retries automatically.
-- Haskell: STM with Control.Concurrent.STM
import Control.Concurrent.STM
type Account = TVar Int
transfer :: Account -> Account -> Int -> STM ()
transfer from to amount = do
fromBal <- readTVar from
if fromBal < amount
then retry -- block until balance changes, then retry
else do
modifyTVar from (subtract amount)
modifyTVar to (+ amount)
-- atomically runs the STM action as a single atomic transaction
main :: IO ()
main = do
alice <- newTVarIO 100
bob <- newTVarIO 50
atomically (transfer alice bob 30)
-- alice = 70, bob = 80; no locks required
STM composes: two STM actions joined with >> or
do-notation form a single larger atomic action. This composability is impossible with
locks - combining two lock-based operations safely requires knowing and acquiring
both locks in the right order.
11.2 Actor Model
In the actor model, the unit of concurrency is an actor: an isolated
entity with its own private state that communicates only by sending and receiving
immutable messages. Because no state is shared, races and locks do not arise. Actors
may run on different threads, processes, or machines.
-- Erlang / Elixir: processes are actors; spawn + send + receive
defmodule Counter do
def start(n), do: spawn(fn -> loop(n) end)
defp loop(n) do
receive do
{:increment, caller} ->
send(caller, {:ok, n + 1})
loop(n + 1)
{:get, caller} ->
send(caller, {:value, n})
loop(n)
end
end
end
pid = Counter.start(0)
send(pid, {:increment, self()})
receive do {:ok, v} -> IO.puts(v) end # 1
-- Rust: channel-based message passing (actor-like)
use std::sync::mpsc;
let (tx, rx) = mpsc::channel();
std::thread::spawn(move || {
tx.send(42).unwrap();
});
let value = rx.recv().unwrap(); // 42
Erlang/OTP and Elixir build fault-tolerant systems from millions of lightweight actors.
Rust’s ownership model enforces actor-like isolation at compile time: a value
moved into a thread cannot be accessed from the sender, eliminating data races
without a runtime.
11.3 Functional Reactive Programming
Functional reactive programming (FRP) models time-varying values
and event streams as first-class values. Instead of callbacks that mutate state, FRP
expresses how outputs depend on inputs declaratively: the runtime propagates changes
automatically when inputs update.
-- Haskell (reflex-frp): Behavior and Event are the two FRP primitives
-- Behavior t a - a value of type a that changes continuously over time t
-- Event t a - a discrete occurrence of type a at a point in time
-- Example concept (simplified pseudocode):
-- mousePos :: Behavior t (Int, Int) -- current mouse position
-- clicks :: Event t () -- fires on each click
-- clickPos :: Event t (Int, Int)
-- clickPos = current mousePos <@ clicks -- sample mousePos at each click
-- JavaScript (RxJS): Observable streams (FRP-inspired)
import { fromEvent, merge } from 'rxjs';
import { map, scan, startWith } from 'rxjs/operators';
const clicks$ = fromEvent(document, 'click');
const count$ = clicks$.pipe(
scan(n => n + 1, 0),
startWith(0)
);
count$.subscribe(n => console.log('clicks:', n));
-- Elm: pure FRP architecture (Model-Update-View)
-- update : Msg -> Model -> Model (pure function, no side effects)
-- view : Model -> Html Msg (pure function, renders current state)
FRP eliminates callback hell and mutable event-handler state. The Elm architecture
(Model-Update-View) is a widely adopted, FRP-inspired pattern for building UIs with
pure functions.
11.4 Dataflow and Streaming
Dataflow programming expresses a computation as a directed graph of
nodes connected by data channels. Nodes fire when their inputs are available; the
scheduler drives execution. This is the model behind many stream-processing systems
(Apache Kafka Streams, Flink, Spark Streaming).
-- Python (asyncio): async generators as lazy dataflow pipelines
import asyncio
async def source(items):
for item in items:
await asyncio.sleep(0) # yield control
yield item
async def transform(stream):
async for item in stream:
yield item * 2
async def sink(stream):
async for item in stream:
print(item)
asyncio.run(sink(transform(source([1, 2, 3]))))
# prints: 2, 4, 6
-- Haskell (pipes / conduit): composable streaming with backpressure
-- Producer -> Pipe -> Consumer
-- Each stage runs only as fast as the downstream consumer demands
-- Apache Flink (Java/Scala): distributed dataflow
-- DataStream<String> stream = env.addSource(kafkaSource);
-- stream.filter(s -> s.startsWith("ERR"))
-- .keyBy(s -> s.split(":")[0])
-- .window(TumblingEventTimeWindows.of(Time.minutes(1)))
-- .sum("count")
-- .addSink(dbSink);
Dataflow and streaming systems express concurrency as data movement through a graph
rather than as threads sharing state. Backpressure - the ability of a slow
consumer to signal a fast producer to slow down - is a key property that
prevents unbounded buffering.
11.5 Epilogue
Declarative concurrency replaces the lock-and-mutate model with safer, more composable
abstractions: STM for shared state, actors for isolated state, FRP for time-varying
values, and dataflow for pipeline parallelism. Each approach eliminates a different
class of concurrency bug by construction. The final chapter examines how declarative
languages express queries and logical rules over structured data.
11.6 References
Haskell STM - Hackage
Erlang Processes - Official Docs
Elm Architecture - Official Guide
RxJS - Overview