Skip to content

Commit 59fd270

Browse files
committed
Refine reactive analysis blog narrative and diagrams
1 parent 37f68e4 commit 59fd270

5 files changed

Lines changed: 72 additions & 7 deletions

File tree

markdown-pages/blog/reactive-analysis.mdx

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Imagine editing a ReScript file and seeing dead code warnings update almost imme
1313

1414
This is what we're bringing to ReScript.
1515

16-
The static analyzer that powers dead code detection is being rebuilt on a **reactive foundation**. When you add a reference to a function, the "unused" warning vanishes immediately. When you remove the last use of a module, the dead code warning appears right away. The analysis stays in sync with your code, updating in real time.
16+
The static analyzer that powers dead code detection is being rebuilt on a **reactive foundation**. When you add a reference to a function, the "unused" warning vanishes quickly. When you remove the last use of a module, the dead code warning appears right away. The analysis stays in sync with your code, updating in real time.
1717

1818
## Why This Matters
1919

@@ -32,21 +32,40 @@ Instead of re-analyzing your entire project on every change, the reactive analyz
3232

3333
The result: analysis that completes in milliseconds for typical edits, even in large codebases.
3434

35+
### A Concrete Update Flow
36+
37+
Suppose you have a call graph where `main` calls `helper`, and `helper` calls `format` and `validate`. In your edit, `validate` starts calling `old`.
38+
39+
With batch analysis, the analyzer scans everything again. With reactive analysis:
40+
41+
1. The edited `.cmt` updates — **FlatMap** extracts the new reference. The compiler already resolved it: the ref knows its target (`old`) and its source location (inside `validate`)
42+
2. **Join** maps that source position to its containing declaration, producing a new graph edge: _validate → old_
43+
3. **Fixpoint** propagates reachability from roots (`main`) along edges — `old` now becomes reachable and enters the live set
44+
4. A final **Classify** step (a join of declarations with the live set) marks `old` as live — so its dead-code warning disappears
45+
46+
The rest of the project graph stays untouched.
47+
3548
### A Glimpse Under the Hood
3649

37-
The technology behind this comes from [SkipLabs](https://skiplabs.io), whose reactive programming primitives were designed for exactly this kind of incremental computation.
50+
Here is a simplified view of the reactive dead code pipeline:
51+
52+
![Simplified reactive dead code pipeline](/blog/reactive-analysis/reactive-pipeline-simple.svg)
3853

3954
The analysis pipeline is built from composable operators:
4055

4156
- **Sources** hold the raw data from your `.cmt` files
42-
- **FlatMap** transforms and extracts declarations and references
43-
- **Join** connects references to their target declarations
44-
- **Union** combines data from different sources
45-
- **Fixpoint** computes transitive closures—which functions call which
57+
- **FlatMap** extracts declarations, references, and annotations from each file's data
58+
- **Union** merges collections — for example, combining value references, type references, and exception references into a unified set
59+
- **Join** maps data across collections — for example, mapping each reference to the declaration that contains it, or partitioning declarations into dead and live using the fixpoint result
60+
- **Fixpoint** computes transitive reachability — starting from root declarations (entry points, annotated `@live`, or externally referenced), following edges to find everything that's live
4661

4762
That last one, **fixpoint**, is particularly interesting. Dead code detection needs to know which declarations are _reachable_ from your entry points. This is a classic graph traversal—but doing it incrementally is hard. When you add one edge to a graph, how do you efficiently update the set of reachable nodes without recomputing from scratch?
4863

49-
The reactive fixpoint operator solves exactly this problem. It maintains the reachable set incrementally, handling both additions and removals efficiently. This is what makes the analysis fast enough for real-time use.
64+
In the running example above, only one new edge (`validate → old`) is added, and fixpoint propagates liveness from that delta.
65+
66+
![Fixpoint: incremental reachability](/blog/reactive-analysis/fixpoint.svg)
67+
68+
The reactive fixpoint operator solves exactly this problem. It takes a set of roots and a set of edges, and maintains the full reachable set. In this example, once `old` becomes reachable through `validate`, fixpoint only propagates through neighbors affected by that new edge, without revisiting unrelated parts of the graph. This is what makes the analysis fast enough for real-time use.
5069

5170
### Glitch-Free by Design
5271

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#e8f5e9', 'primaryTextColor': '#102a43', 'primaryBorderColor': '#4caf50', 'lineColor': '#486581'}}}%%
2+
flowchart LR
3+
A_main["<b>main</b><br/><small>root</small>"]
4+
A_helper["helper"]
5+
A_format["format"]
6+
A_validate["validate"]
7+
A_old["old"]
8+
9+
A_main -- "wave 1" --> A_helper
10+
A_helper -- "wave 2" --> A_format
11+
A_helper -- "wave 2" --> A_validate
12+
A_validate -. "new edge: validate → old" .-> A_old
13+
14+
classDef rootNode fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px,color:#102a43
15+
classDef liveNode fill:#e8f5e9,stroke:#4caf50,stroke-width:2px,color:#102a43
16+
classDef newlyLive fill:#fff9c4,stroke:#f9a825,stroke-width:2.5px,color:#102a43
17+
18+
class A_main rootNode
19+
class A_helper,A_format,A_validate liveNode
20+
class A_old newlyLive
Lines changed: 1 addition & 0 deletions
Loading

0 commit comments

Comments
 (0)