Idea

How IO values are built, run and analyzed

When a call performs its effect the moment it runs, the only way to list what a program can do is to trace every path through it. We avoid that by treating effects as values. In Yichus’s Bosatsu programs, IO[A] is a description of a computation that produces A. Constructing an IO does not execute its described effects. Those operations execute when the runtime interprets the chain. Registered IO primitives remain visible to typed static analysis. (Reading the code below: x <- e.flat_map() is a do-block, meaning "run e, bind its result to x". It desugars to e.flat_map(x -> <rest of block>); the continuation lambda is supplied by the notation, which is why flat_map() appears with no written argument. Snippets are trimmed from the demo's shipped source, demos/ui/io-viz.bosatsu, with each trim stated in a comment.)

Credit: effects as values is the IO monad lineage of Moggi and Wadler, used in Haskell, Elm and Haxl. Yichus reads those values statically to find every effect a program can perform.

Code & run Inspect and run the IO illustration

The three contracts below are: construction is deferred, flat_map sequences IO values, and the Matchless sidecar reports typed IO sites and composition edges. The deferred-mutation test covers the first contract; the analysis exporter owns the third.

Generated IO Lifecycle Illustration

1

IO Construction and Runtime Interpretation

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

# A top-level IO value from the demo. state(...) builds a
# DESCRIPTION of "allocate a cell" -- nothing is allocated
# at this line.
chain_text: IO[State[String]] = state("No IO chain built yet.")

# Inside a handler: run that description, bind the cell,
# then build (not perform) a write. (chain_value is the
# handler's parameter -- full context in step 2.)
ct <- chain_text.flat_map()
ct.write(chain_value)   # returns IO[Unit], not Unit

This is different from a React state setter, which enqueues an opaque update the moment you call it. In Yichus, effects are first-class IO values you can pass around and compose. Takeaway: constructing this state write is separate from running it. See the JVM external and its execution-boundary test.

React state-setter timing boundary
A React setter requests a state update; React can batch requests and skip work when the new state is unchanged. See React’s setter reference. Yichus’s flat_map composes an IO value with a continuation. Static analysis can inspect supported sites and relationships, but later IO values may depend on earlier runtime results.
2

Composing IO Chains with flat_map

flat_map 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.

# The demo's set_stage helper -- every button handler
# (do_create, do_run, do_succeed) is one call to it. Trimmed:
# the real signature takes 11 values (one per cell); three
# cells shown here. step_count and status_text are top-level
# cells declared like chain_text in step 1.
def set_stage(step_value: Int, chain_value: String,
    status_value: String) -> IO[Unit]: (
  sc <- step_count.flat_map()   # 1. bind the step-count cell
  ct <- chain_text.flat_map()   # 2. bind the chain-display cell
  st <- status_text.flat_map()  # 3. bind the status cell
  _ <- sc.write(step_value).flat_map()   # 4. write step number
  _ <- ct.write(chain_value).flat_map()  # 5. write chain display
  st.write(status_value)                 # 6. last IO in the chain
)

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. Takeaway: the continuation determines ordering. Read the full generated-demo source instead of treating the trimmed block as the complete handler.

3

Stage Box Style Bindings

The demo visualizes the IO lifecycle as a pipeline of stage boxes. Each stage uses style-background bindings, so the box colors are reactive state instead of 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's color: the caller passes the value read from
# state, and the compiler binds this property to that state
def stage_box(label: String, bg: String) -> VNode:
  h("div", [
    ("style-background", bg)
  ], [text(label)])

When you click "Create IO," the handler writes new colors to the stage background states. These scalar style states have direct binding routes to style.background. A missing route is an error, not a fallback render. Takeaway: this demo's color cells exercise generated style bindings. Inspect the runtime dispatch and the advertised-flow E2E.

Scalar style-binding dispatch scope
In this generated demo, the idle_bg scalar binding identifies one style.background target. Other state shapes can use declared full-render or canvas routes.
4

Illustrative IO Chain Display

Below the pipeline, the demo shows the growing IO chain as display pseudocode (the display spells it flatMap; Bosatsu's operator is flat_map). httpGet and Response in this diagram are teaching names; they are not calls made by io-viz.bosatsu and are not a copy-pasteable Yichus API example.

pure("fetch /api/data") → 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]

Takeaway: read the arrows as sequencing, not as recorded runtime output. The real demo handler updates local UI state; its exact chain is in io-viz.bosatsu.

5

IO and JavaScript Execution Semantics

PropertyYichus contractScope
ExecutionThe runtime interprets IO valuesRegistered primitives must honor deferred execution
Compositionflat_map passes a result to a continuationThe next IO can depend on that runtime result
Static analysisExports typed IO sites and composition edgesLimited to the operations and structure the analyzer models
TestingCheck construction and execution separatelyRuntime tests are still needed to establish actual effects
ProvenanceSupported sites carry compiler and runtime metadataMetadata does not prove application correctness

Takeaway: typed IO structure gives the exporter explicit sites and composition edges, but this does not by itself prove the correctness or completeness of every application derivation. Read the sidecar exporter and the automatic Why? boundary.

Matchless IO analysis sidecar fields and command
The yichus matchless command emits a machine-readable analysis file alongside the compiled IR. From a clone of the repo (after sbt assembly; commands run as java -jar target/scala-*/yichus.jar …):
yichus matchless --format json --output out \
  demos/pricing/pricing.bosatsu demos/pricing/report.bosatsu
# writes out.matchless, out.matchless.meta,
#   and out.matchless.analysis.json
The analysis 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. The exporter source defines the supported fields.
UI write execution and binding-map dispatch
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.

Where this fits