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
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
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.
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.
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
idle_bg scalar
binding identifies one style.background target.
Other state shapes can use declared full-render or canvas routes.
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.
IO and JavaScript Execution Semantics
| Property | Yichus contract | Scope |
|---|---|---|
| Execution | The runtime interprets IO values | Registered primitives must honor deferred execution |
| Composition | flat_map passes a result to a continuation | The next IO can depend on that runtime result |
| Static analysis | Exports typed IO sites and composition edges | Limited to the operations and structure the analyzer models |
| Testing | Check construction and execution separately | Runtime tests are still needed to establish actual effects |
| Provenance | Supported sites carry compiler and runtime metadata | Metadata 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
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.jsonThe 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