This content originally appeared on DEV Community and was authored by Dario Mannu
Most programming paradigms evolve around a single idea — whether that’s composition, purity, dataflow, or state isolation. But there’s one recurring question that keeps surfacing in every new language or framework: how do we manage effects?
Effects are the unavoidable “real world” part of computation. They’re what make your program do something rather than just describe something. Logging, rendering, HTTP calls, I/O, and even user interaction — all are effects.
Functional programming tried to solve this decades ago. Imperative programming ignored the problem entirely. Stream-Oriented Programming (SP) does something different: it turns effects into maps.
The Problem with Traditional Effect Management
If you’ve ever written pure functional code, you know that separating logic from effects isn’t as simple as it sounds. In theory, functions should be pure and side-effects handled by monadic constructs such as IO, Task, Future, or similar abstractions. In practice, the code often turns into a tangled web of type constructors and continuations, all just to print a line or fetch data.
The monad pattern elegantly expresses effectful computation as data, but it also forces you to wrap and unwrap effects through chained continuations. Free and freer monads attempted to improve this by deferring interpretation — allowing you to “describe” effects and then interpret them later — but this only moves the complexity around. The price is boilerplate and a significant gap between your logic and what it actually does.
The essence of the problem: you have to manually organise and synchronise effects.
This is the hidden cost of functional purity. You must either:
- Thread effects explicitly through monadic bind chains (
flatMap/>>=), or - Defer them into abstract syntax trees (via Free/Freeer monads), or
- Manage continuations to preserve purity across asynchronous boundaries.
That’s a lot of ceremony just to say “print this after reading that”.
HTML: The Most Powerful Effect Language Ever Created
Web developers, ironically, have been dealing with effects all along — effortlessly.
Every <button>, <input>, and <img> in HTML is an effect declaration. Each element describes what the program should do in the world — display text, handle clicks, submit forms — while keeping logic and structure separate.
HTML doesn’t execute effects, it declares them. That’s the key.
Stream-Oriented Programming builds on this insight. Instead of separating logic and effects through abstract monadic constructs, SP integrates them using Effect Maps — templates that directly describe how streams (logic) connect to the world (effects).
In this view, HTML isn’t just a markup language — it’s an Effect Declaration Language. Every element, attribute, and event handler is an entry in the global effect map of your program.
From Functions to Streams
In SP, logic isn’t built from functions, it’s built from streams. Streams are continuous values over time — observable sequences that represent not just data, but evolution.
The major conceptual shift is this:
Instead of passing functions around, you pass streams that continuously emit values.
That means composition happens naturally. When you connect two streams, their relationship over time defines the behaviour — without explicit continuations or monadic binds.
So while in functional programming you might see something like:
readLine()
.then(input => input.toUpperCase())
.then(println)
In SP, you’d simply declare:
stream echo = pipeline(
map(source => `You said: ${source}`)
)
effects`
stdin=${echo}
stdout=${echo}
`
That’s it. No continuation, no monad, no runtime interpreter.
The template itself defines the effect topology: how data moves between logical streams and the external world.
What Is an Effect Map?
An Effect Map is a declarative structure that connects streams (which represent your logic) to ports (which represent the external world). Think of it as a topological description of your application’s I/O system.
Each key in an effect map corresponds to an effect endpoint — for instance, a DOM element, a console output, or a cloud event stream. Each value corresponds to a stream that feeds or reacts to that endpoint.
A minimal web example:
<script>
const
</script>
<main>
<input id="name" placeholder="Type your name" />
<h1>Hello <output id="name"></output></h1>
</main>
That’s already an effect map. The <input> defines an input stream, the <output> defines a sink, and their IDs implicitly connect them.
The same concept works outside the browser:
main {
stream echo = pipeline(
map(source => `You said: ${source}`)
)
return native_effect_map`
stdin=${echo}
stdout=${echo}
`
}
Here, stdin and stdout are effect ports. The template connects them to the stream logic. No runtime interpretation is needed — the structure itself is the map.
Why It Works So Well
Because SP treats everything as a stream, time is a first-class dimension. This removes the need for continuations entirely. There’s no “after this, do that” — there’s just data flowing through pipelines.
Effects are synchronised not by explicit control flow, but by the natural composition of streams. When one emits, another reacts. When nothing emits, nothing happens.
This makes SP radically simpler for effectful programs:
- No continuation management — flow is inherent to streams.
- No monadic wrapping — effects are declared directly.
- No separate interpreter — the template is the effect map.
You get a model that is simultaneously reactive and declarative, without losing structural clarity.
Comparing to Free and Freer Monads
Free and freer monads aim to separate effect description from effect interpretation. You define an algebra of effects (e.g., Read, Write, Delay), build a program as a tree of these, and then provide an interpreter that executes them.
Example (simplified pseudocode):
data Console a
= Read (String -> a)
| Write String a
type Program = Free Console
program = do
line <- liftF (Read id)
liftF (Write ("You said: " ++ line))
Elegant in theory, but still needs a runtime interpreter that walks the tree and decides when to perform each effect. It’s pure, but detached.
In SP, the same idea collapses into something much smaller:
main {
stream echo = pipeline(
map(source => `You said: ${source}`)
)
return native_effect_map`
stdin=${echo}
stdout=${echo}
`
}
The interpretation is implicit in the structure. The “effect map” directly binds streams to their effect endpoints. There’s no need to construct an AST or fold over it — the runtime is the topology itself.
So where monadic approaches describe how to sequence effects, SP describes how they’re connected.
Sequence is emergent, not prescribed.
The (inputs) => Template Maps Pattern
At its core, every SP program follows the same structure:
(inputs) => Template Maps
Inputs are streams from the outside world — user events, messages, data feeds. The template map declares how these inputs feed into the application’s reactive logic, and how their results feed back out.
This can scale from a trivial echo example to full reactive systems, web apps, or distributed services. The underlying concept stays the same: every program is a composition of streams mapped onto effect templates.
That means an SP runtime for native, cloud, or server environments can use an HTML-like template syntax to describe effect connections — stdin, stdout, file, socket, topic, db — without needing to reinvent the language each time.
HTML already solved the hard part: declaratively describing what should happen and where.
Generalising Beyond the Browser
Imagine defining a microservice using the same principles:
stream users = pipeline(
query("users")
)
stream active = users.pipe(
filter(u => u.active)
)
effects`
GET /api/users=${users}
stdout=${active}
`
The syntax remains simple. Streams define what happens, templates define where it happens.
If you swap out the effects template for another environment, the same logic runs elsewhere — perhaps in a CLI, a worker, or a serverless endpoint.
This is what makes the model portable. The HTML-like language isn’t tied to the DOM; it’s just a declarative schema for effects.
Why This Matters
We’ve reached a point where logic is easy, but effects are hard. In most modern systems, the actual business logic of a program is tiny compared to the infrastructure code needed to orchestrate events, side-effects, and state synchronisation.
Stream-Oriented Programming cuts through that by collapsing effect management into its natural form — a mapping between streams and their destinations. You no longer “manage” effects; you describe them.
This has several implications:
- Composability: effect maps compose just like HTML fragments.
- Transparency: every effect is visible in the template; nothing hidden in callbacks.
- Portability: the same logic can target browser, server, or native backends.
- Testability: streams can be simulated and observed without touching real I/O.
- Extensibility: new effect types can be declared simply by extending the template vocabulary.
From Templates to Topologies
Effect maps aren’t just about syntax — they’re about topology.
They describe how data flows through a system, not just what functions run. This allows compilers or runtimes to visualise, optimise, or even distribute the system automatically.
An SP compiler could, for instance:
- Detect that two streams form a feedback loop and isolate them.
- Parallelise independent streams automatically.
- Move effects to edge nodes or client devices dynamically.
Once the program is a map, the runtime can reason about it.
That’s something monadic chains can’t do, because the structure is hidden inside control flow.
The Future of Effect Languages
HTML’s accidental genius was that it separated description from execution. SP borrows that insight and extends it to all domains. Whether it’s rendering UI, running cloud workers, or wiring hardware, the same idea applies: effects are declared, not controlled.
As programming continues to shift toward reactivity and distribution, the need for clear, declarative effect systems grows. Stream-Oriented Programming offers a model that is not only simpler, but more general: an architecture where every effect is a node in a living map, every logic is a stream, and every application is the composition of both.
Closing Thought
Effect Maps aren’t an abstraction over effects; they’re an expression of them.
By treating logic as streams and using templates as maps, Stream-Oriented Programming eliminates one of the oldest dichotomies in computer science — between pure logic and messy side-effects — and replaces it with a single, flowing system that simply says:
inputs → streams → effects
And perhaps that’s all we ever needed.
Learn More
![]() |
Stream Oriented Programming: an introduction |
![]() |
From callbacks to callforwards: reactivity made simple? |
![]() |
State Management and the Mixed Wastewater Problem |
This content originally appeared on DEV Community and was authored by Dario Mannu


