← Back to Yichus

IO Effects Are Values

In Bosatsu, IO[A] is a description of a computation that produces A. Constructing an IO does nothing -- effects only execute when the runtime interprets the IO chain. This makes side effects composable, testable, and statically analyzable.

No Side Effects At Construction Composable IO Chains Static Analysis

Live Demo

1

IO Is a Value, Not an Action

When you write state.write("hello") in Bosatsu, nothing happens. You get back an IO[Unit] -- a data structure describing "write hello to this state." The write does not execute until the runtime interprets the IO.

# This does NOT write anything. It creates a value.
def do_create(_: Unit) -> IO[Unit]: (
  s <- io_state.flatMap()
  s.write("fetch:start")   # returns IO[Unit], not Unit
)

This is fundamentally different from JavaScript's setState("hello"), which mutates immediately. In Bosatsu, effects are first-class values you can pass around, compose, and analyze before execution.

Compare to React
React's useState setter calls are immediate mutations. There's no way to inspect, compose, or delay a setState call -- once you call it, the mutation is queued. In Bosatsu, IO values can be composed with flatMap and the full chain is visible to the compiler before anything executes.
2

Composing IO Chains with flatMap

flatMap sequences IO values. The result is a new IO value describing "do A, then use A's result to build B, then do B." The chain is a data structure the runtime walks through.

# Three IO operations chained together
def do_succeed(_: Unit) -> IO[Unit]: (
  sn <- stage_name.flatMap()        # 1. get state ref
  _ <- sn.write("success").flatMap()  # 2. write stage name
  _ <- set_all_bg(...).flatMap()     # 3. update colors
  st.write("Step 3: completed")      # 4. update status
)

Each <- binds the result of the previous IO, feeding it forward. The entire chain is a single IO[Unit] value -- a recipe for the runtime to follow.

3

Visual Pipeline: Seeing the State Machine

The demo visualizes the IO lifecycle as a pipeline of stage boxes. Each stage uses style-background bindings -- the box colors are reactive state, not hardcoded CSS.

# Each stage box has its own background color state
idle_bg: IO[State[String]] = state("#667eea")
created_bg: IO[State[String]] = state("#334155")

# The box reads its color from state
def stage_box(label: String, bg: String) -> VNode:
  h("div", [
    ("style-background", bg)  # bound to state!
  ], [text(label)])

When you click "Create IO," the handler writes new colors to each stage's background state. The binding map dispatches these writes directly to the DOM's style.background property -- no re-render, no diff.

Compare to React
In React, changing a pipeline box's color means updating state, triggering a re-render of the entire component tree, diffing the virtual DOM, and patching the changed nodes. With Yichus's binding map, a state write to idle_bg updates exactly one style.background property on exactly one DOM element. O(1), no diffing.
4

The IO Chain Display

Below the pipeline, the demo shows the growing IO chain as pseudocode. This mirrors what the runtime actually sees: a chain of flatMap calls building up a sequence of effects.

pure("fetch /api") → flatMap(httpGet) → flatMap(state.write)

Step 1: pure("fetch /api/data") : IO[String]
Step 2: flatMap(url -> httpGet(url)) : IO[Response]
Step 3: flatMap(resp -> state.write(resp.body)) : IO[Unit]

At each step, the IO chain grows. The runtime only executes when the full chain is assembled and returned to the event handler dispatch.

5

Why This Matters

PropertyIO (Bosatsu)Imperative (JS/React)
When effects runOnly when runtime interpretsImmediately on call
ComposabilityflatMap chains, first-class valuesCallback nesting or async/await
Static analysisCompiler sees all effectsEffects invisible to compiler
TestabilityInspect IO chain without executingMust mock side effects
ProvenanceEvery write traced to its sourceNo built-in tracking

IO-as-values is the foundation of Yichus's provenance tracking. Because every mutation is a described value, the compiler can trace which inputs produced which outputs -- enabling features like AI code detection and audit trails.

New: Matchless IO analysis sidecar
The yichus matchless command now emits <output>.matchless.analysis.json in addition to the IR and metadata files. The sidecar includes:
  • IO-site index (kind, primitive, region, state ref, inner type)
  • IO composition graph (flat_map causal edges, sequence groups)
  • Per-binding purity and dependency summaries
This gives downstream tools a typed, machine-readable effect topology without reimplementing IO extraction.
Learn more: IO in the explainer
The How It Works page walks through the full pipeline from Bosatsu source to DOM update, showing exactly how IO values are compiled, the binding map is generated, and the runtime dispatches writes.