From 4e6ebe00b00489d377fe6c3874d4e8a770e4c0e4 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 6 May 2026 19:46:51 +0000 Subject: [PATCH 01/16] docs: emission-intuition diagram series (4-step visual story) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four progressive SVGs in docs/demos/emission-intuition/ that show, without math or LOC counters, how the rim (intent + LanguageSpec rules) stays visually small while the generated-code grid grows as targets and types multiply. Reader sees the asymmetry by eye, not by counting. 01-start-small.svg — 1 type × 1 language → 1 cell (mechanism) 02-add-languages.svg — same type × 3 languages → 3 cells (column grows) 03-add-types.svg — 3 types × 3 languages → 9 cells (rules unchanged) 04-now-scale.svg — 8 types × 4 languages → 32 cells (rim stays small) Draft PR for review of visual grammar and intent-language wording before broader use. --- .../emission-intuition/01-start-small.svg | 45 ++++ .../emission-intuition/02-add-languages.svg | 69 ++++++ .../demos/emission-intuition/03-add-types.svg | 131 +++++++++++ .../demos/emission-intuition/04-now-scale.svg | 220 ++++++++++++++++++ 4 files changed, 465 insertions(+) create mode 100644 docs/demos/emission-intuition/01-start-small.svg create mode 100644 docs/demos/emission-intuition/02-add-languages.svg create mode 100644 docs/demos/emission-intuition/03-add-types.svg create mode 100644 docs/demos/emission-intuition/04-now-scale.svg diff --git a/docs/demos/emission-intuition/01-start-small.svg b/docs/demos/emission-intuition/01-start-small.svg new file mode 100644 index 00000000000..7a937e6e160 --- /dev/null +++ b/docs/demos/emission-intuition/01-start-small.svg @@ -0,0 +1,45 @@ + + + + + + + + + Start small. + One type. One language. One piece of code. + + YOU WRITE + + + .dag + type Pair<A, B> { + first: A + second: B + } + + + + COMPILER GENERATES + + + RUST + pub struct Pair<A, B> { + pub first: A, + pub second: B, + } + diff --git a/docs/demos/emission-intuition/02-add-languages.svg b/docs/demos/emission-intuition/02-add-languages.svg new file mode 100644 index 00000000000..59b261ba869 --- /dev/null +++ b/docs/demos/emission-intuition/02-add-languages.svg @@ -0,0 +1,69 @@ + + + + + + + + + Add a language. Then another. + Same type. Three languages. Three pieces of code. + + YOU WRITE + + + .dag + type Pair<A, B> { + first: A + second: B + } + + + + + + + COMPILER GENERATES + + + + + RUST + pub struct Pair<A, B> { + pub first: A, + pub second: B, + } + + + + + PYTHON + @dataclass + class Pair(Generic[A, B]): + first: A + second: B + + + + + GO + type Pair[A, B any] struct { + First A + Second B + } + + Adding a language adds one column. Your intent didn't change. + diff --git a/docs/demos/emission-intuition/03-add-types.svg b/docs/demos/emission-intuition/03-add-types.svg new file mode 100644 index 00000000000..7f1d885b7a7 --- /dev/null +++ b/docs/demos/emission-intuition/03-add-types.svg @@ -0,0 +1,131 @@ + + + + + + Add types. Same languages. + Three types × three languages — nine pieces of code. The rules didn't change. + + YOU WRITE + COMPILER GENERATES + + + + + .dag + type Pair<A, B> { + first: A + second: B + } + + + + + RUST + pub struct Pair<A, B> { + pub first: A, + pub second: B, + } + + + + + PYTHON + @dataclass + class Pair(Generic[A, B]): + first: A + second: B + + + + + GO + type Pair[A, B any] struct { + First A + Second B + } + + + + + .dag + type Sum<A, B> = + Left { value: A } + | Right { value: B } + + + + + RUST + pub enum Sum<A, B> { + Left { value: A }, + Right { value: B }, + } + + + + + PYTHON + @dataclass + class Left(Generic[A]): value: A + class Right(Generic[B]): value: B + Sum = Union[Left[A], Right[B]] + + + + + GO + type Sum[A, B any] interface{ + isSum() } + type Left[A any] struct{ V A } + type Right[B any] struct{ V B } + + + + + .dag + type Option<T> = + Some { value: T } + | None + + + + + RUST + pub enum Option<T> { + Some { value: T }, + None, + } + + + + + PYTHON + @dataclass + class Some(Generic[T]): value: T + class _None: pass + Option = Union[Some[T], _None] + + + + + GO + type Option[T any] interface{ + isOpt() } + type Some[T any] struct{ V T } + type None_ struct{} + + Adding a type adds one row. The columns (the rules) are unchanged. + diff --git a/docs/demos/emission-intuition/04-now-scale.svg b/docs/demos/emission-intuition/04-now-scale.svg new file mode 100644 index 00000000000..776e5e1e953 --- /dev/null +++ b/docs/demos/emission-intuition/04-now-scale.svg @@ -0,0 +1,220 @@ + + + + + + Now scale. + Eight types × four languages — 32 pieces of code. Your intent and the rules stayed small. + + YOU WRITE — 8 TYPES + COMPILER GENERATES — ONE CELL PER (TYPE × LANGUAGE) + + + + RUST + + PYTHON + + GO + + TYPESCRIPT + + + + + + PAIR + type Pair<A, B> { + first: A; second: B } + + pub struct Pair<A, B> { + pub first: A, + pub second: B, + } + + @dataclass + class Pair(Generic[A,B]): + first: A; second: B + + type Pair[A,B any] struct{ + First A; Second B + } + + interface Pair<A, B> { + first: A; second: B; + } + + + + SUM + type Sum<A, B> = + Left{v:A} | Right{v:B} + + pub enum Sum<A, B> { + Left{v:A}, Right{v:B} + } + + Sum = Union[ + Left[A], Right[B] + ] + + type Sum[A,B any] interface{ + isSum() // + Left, Right + } + + type Sum<A, B> = + | { kind:'L', v:A } + | { kind:'R', v:B }; + + + + OPTION + type Option<T> = + Some{v:T} | None + + pub enum Option<T> { + Some{v:T}, None + } + + Option = Union[ + Some[T], _None + ] + + type Option[T any] interface{ + isOpt() // + Some, None_ + } + + type Option<T> = + | { kind:'Some', v:T } + | { kind:'None' }; + + + + RESULT + type Result<T, E> = + Ok{v:T} | Err{e:E} + + pub enum Result<T, E> { + Ok{v:T}, Err{e:E} + } + + Result = Union[ + Ok[T], Err[E] + ] + + type Result[T,E any] interface{ + isResult() // + Ok, Err + } + + type Result<T, E> = + | { kind:'Ok', v:T } + | { kind:'Err', e:E }; + + + + LIST + type List<T> = + Cons{h:T,t:List<T>} | Nil + + pub enum List<T> { + Cons{h:T,t:Box<List<T>>}, + Nil } + + @dataclass + class Cons: h:T; t:'List[T]' + List = Union[Cons[T], Nil] + + type List[T any] interface{ + isList() // recursive Cons, + } // Nil_ + + type List<T> = + | { kind:'Cons', h:T, + t:List<T> } | {kind:'Nil'}; + + + + TREE + type Tree<T> = Leaf{v:T} + | Node{l:Tree,r:Tree} + + pub enum Tree<T> { + Leaf{v:T}, + Node{l:Box<..>, r:Box<..>} } + + @dataclass + class Leaf: v:T + Tree = Union[Leaf[T], Node[T]] + + type Tree[T any] interface{ + isTree() // Leaf, Node + } + + type Tree<T> = + | { kind:'Leaf', v:T } + | { kind:'Node', ... }; + + + + EITHER + type Either<L, R> = + Lhs{v:L} | Rhs{v:R} + + pub enum Either<L, R> { + Lhs{v:L}, Rhs{v:R} + } + + Either = Union[ + Lhs[L], Rhs[R] + ] + + type Either[L,R any] interface{ + isEither() // Lhs, Rhs + } + + type Either<L, R> = + | { kind:'Lhs', v:L } + | { kind:'Rhs', v:R }; + + + + NONEMPTY + type NonEmpty<T> { + head: T; tail: List<T> } + + pub struct NonEmpty<T> { + pub head: T, + pub tail: List<T>, + } + + @dataclass + class NonEmpty(Generic[T]): + head: T; tail: List[T] + + type NonEmpty[T any] struct{ + Head T; Tail List[T] + } + + interface NonEmpty<T> { + head: T; + tail: List<T>; + } + + A new type adds one row. A new language adds one column. The grid grows fast — your intent doesn't. + From d0c748f93e9d010df694a28fda36e19424b9764f Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 6 May 2026 19:54:01 +0000 Subject: [PATCH 02/16] docs: refine emission-intuition diagrams MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per review feedback: - Drop Go and TypeScript; focus on Rust + Python only. - Add a LOGIC row separating .dag compositional models (TYPES) from small consumer code that actually calls the type — so the reader sees both the type definition AND a function using it emitted per language. - The grid is now 2 rows (TYPES, LOGIC) × (1 .dag column + N target columns), so the right-side area scales with both the type count (vertical) and the language count (horizontal). 01-start-small — 1 type + 1 small consumer × Rust 02-add-languages — same source × Rust + Python (column added) 03-add-types — 3 types + 3 consumers × Rust + Python (rows grew) 04-now-scale — 6 types + 6 consumers × Rust + Python (full modules) --- .../emission-intuition/01-start-small.svg | 78 ++-- .../emission-intuition/02-add-languages.svg | 108 +++--- .../demos/emission-intuition/03-add-types.svg | 210 +++++----- .../demos/emission-intuition/04-now-scale.svg | 361 ++++++++---------- 4 files changed, 379 insertions(+), 378 deletions(-) diff --git a/docs/demos/emission-intuition/01-start-small.svg b/docs/demos/emission-intuition/01-start-small.svg index 7a937e6e160..3bcff4e9aee 100644 --- a/docs/demos/emission-intuition/01-start-small.svg +++ b/docs/demos/emission-intuition/01-start-small.svg @@ -1,45 +1,65 @@ - + - - - Start small. - One type. One language. One piece of code. - - YOU WRITE - - - .dag - type Pair<A, B> { - first: A - second: B - } - - - - COMPILER GENERATES - - - RUST - pub struct Pair<A, B> { - pub first: A, - pub second: B, - } + One type. One little function that uses it. One language. The compiler emits both. + + + TYPES — the compositional model + + + .dag + + type Sum<A, B> = + Left { value: A } + | Right { value: B } + + + RUST + + pub enum Sum<A, B> { + Left { value: A }, + Right { value: B }, + } + + + LOGIC — someone actually calling it + + + .dag + + fn unwrap_str( + s: Sum<Str, Str> + ) -> Str = + match s { + Left { value } => value + Right { value } => value + } + + + RUST + + pub fn unwrap_str( + s: Sum<String, String>, + ) -> String { + match s { + Sum::Left { value } => value, + Sum::Right { value } => value, + } + } + + A type and one consumer of it. The compiler emits a working Rust module from the .dag. diff --git a/docs/demos/emission-intuition/02-add-languages.svg b/docs/demos/emission-intuition/02-add-languages.svg index 59b261ba869..a20856da341 100644 --- a/docs/demos/emission-intuition/02-add-languages.svg +++ b/docs/demos/emission-intuition/02-add-languages.svg @@ -1,69 +1,83 @@ - + - - - - Add a language. Then another. - Same type. Three languages. Three pieces of code. + Add a language. + Same type, same consumer. Two languages — two working modules. - YOU WRITE - - - .dag - type Pair<A, B> { - first: A - second: B - } + + TYPES — the compositional model - - - - + + .dag + + type Sum<A, B> = + Left { value: A } + | Right { value: B } - COMPILER GENERATES + + RUST + + pub enum Sum<A, B> { + Left { value: A }, + Right { value: B }, + } - - - - RUST - pub struct Pair<A, B> { - pub first: A, - pub second: B, - } + + PYTHON + + @dataclass + class Left(Generic[A]): value: A + class Right(Generic[B]): value: B + Sum = Union[Left[A], Right[B]] - - - - PYTHON - @dataclass - class Pair(Generic[A, B]): - first: A - second: B + + LOGIC — someone actually calling it - - - - GO - type Pair[A, B any] struct { - First A - Second B - } + + .dag + + fn unwrap_str( + s: Sum<Str, Str> + ) -> Str = + match s { + Left { value } => value + Right { value } => value + } - Adding a language adds one column. Your intent didn't change. + + RUST + + pub fn unwrap_str( + s: Sum<String, String>, + ) -> String { + match s { + Sum::Left { value } => value, + Sum::Right { value } => value, + } + } + + + PYTHON + + def unwrap_str( + s: Sum[str, str], + ) -> str: + match s: + case Left(value=v): return v + case Right(value=v): return v + + Source unchanged. Adding a language adds one column on the right — both rows fill in. diff --git a/docs/demos/emission-intuition/03-add-types.svg b/docs/demos/emission-intuition/03-add-types.svg index 7f1d885b7a7..f57e509711e 100644 --- a/docs/demos/emission-intuition/03-add-types.svg +++ b/docs/demos/emission-intuition/03-add-types.svg @@ -1,13 +1,12 @@ - + - Add types. Same languages. - Three types × three languages — nine pieces of code. The rules didn't change. + Add types. + Three types, three small consumers. Same two languages — but each side has more to emit. - YOU WRITE - COMPILER GENERATES + + TYPES — three compositional models - - - - .dag - type Pair<A, B> { - first: A - second: B - } + + .dag + + type Pair<A, B> { + first: A; second: B + } + type Sum<A, B> = + Left { value: A } + | Right { value: B } + type Option<T> = + Some { value: T } + | None - - - - RUST - pub struct Pair<A, B> { - pub first: A, - pub second: B, - } + + RUST + + pub struct Pair<A, B> { + pub first: A, pub second: B, + } + pub enum Sum<A, B> { + Left { value: A }, + Right { value: B }, + } + pub enum Option<T> { + Some { value: T }, None, + } - - - - PYTHON - @dataclass - class Pair(Generic[A, B]): - first: A - second: B + + PYTHON + + @dataclass + class Pair(Generic[A,B]): + first: A; second: B + @dataclass + class Left(Generic[A]): value: A + class Right(Generic[B]): value: B + Sum = Union[Left[A], Right[B]] + @dataclass + class Some(Generic[T]): value: T + Option = Union[Some[T], None] - - - - GO - type Pair[A, B any] struct { - First A - Second B - } + + LOGIC — three small consumers - - - - .dag - type Sum<A, B> = - Left { value: A } - | Right { value: B } + + .dag + + fn swap<A, B>(p: Pair<A, B>) + -> Pair<B, A> = + Pair { first: p.second, + second: p.first } + fn unwrap_str( + s: Sum<Str, Str>) -> Str = + match s { + Left { value } => value + Right { value } => value + } + fn or_else<T>( + o: Option<T>, d: T) -> T = + match o { + Some { value } => value + None => d + } - - - - RUST - pub enum Sum<A, B> { - Left { value: A }, - Right { value: B }, - } + + RUST + + pub fn swap<A, B>(p: Pair<A, B>) + -> Pair<B, A> { + Pair { first: p.second, + second: p.first } + } + pub fn unwrap_str( + s: Sum<String, String>) -> String { + match s { + Sum::Left { value } => value, + Sum::Right { value } => value, + } + } + pub fn or_else<T>( + o: Option<T>, d: T) -> T { + match o { + Option::Some{value}=>value, + Option::None=>d } } - - - - PYTHON - @dataclass - class Left(Generic[A]): value: A - class Right(Generic[B]): value: B - Sum = Union[Left[A], Right[B]] + + PYTHON + + def swap(p: Pair[A, B]) + -> Pair[B, A]: + return Pair(first=p.second, + second=p.first) + def unwrap_str( + s: Sum[str, str]) -> str: + match s: + case Left(value=v): return v + case Right(value=v): return v + def or_else( + o: Option[T], d: T) -> T: + match o: + case Some(value=v): return v + case None_(): return d - - - - GO - type Sum[A, B any] interface{ - isSum() } - type Left[A any] struct{ V A } - type Right[B any] struct{ V B } - - - - - .dag - type Option<T> = - Some { value: T } - | None - - - - - RUST - pub enum Option<T> { - Some { value: T }, - None, - } - - - - - PYTHON - @dataclass - class Some(Generic[T]): value: T - class _None: pass - Option = Union[Some[T], _None] - - - - - GO - type Option[T any] interface{ - isOpt() } - type Some[T any] struct{ V T } - type None_ struct{} - - Adding a type adds one row. The columns (the rules) are unchanged. + Source grew by a few lines per type. The right side grew by a whole module per language. diff --git a/docs/demos/emission-intuition/04-now-scale.svg b/docs/demos/emission-intuition/04-now-scale.svg index 776e5e1e953..e978a2bc253 100644 --- a/docs/demos/emission-intuition/04-now-scale.svg +++ b/docs/demos/emission-intuition/04-now-scale.svg @@ -1,18 +1,14 @@ - + Now scale. - Eight types × four languages — 32 pieces of code. Your intent and the rules stayed small. + Six types and six small consumers. Same two languages. The right side is a working module per language — your source stayed compact. - YOU WRITE — 8 TYPES - COMPILER GENERATES — ONE CELL PER (TYPE × LANGUAGE) + + TYPES — six compositional models - - - RUST - - PYTHON - - GO - - TYPESCRIPT + + .dag + + type Pair<A, B> { + first: A; second: B } + type Sum<A, B> = + Left { value: A } + | Right { value: B } + type Option<T> = + Some { value: T } | None + type Result<T, E> = + Ok { value: T } + | Err { error: E } + type List<T> = + Cons { head: T, + tail: List<T> } + | Nil + type Tree<T> = + Leaf { value: T } + | Node { left: Tree<T>, + right: Tree<T> } - + + RUST + + pub struct Pair<A, B> { + pub first: A, pub second: B } + pub enum Sum<A, B> { + Left { value: A }, + Right { value: B } } + pub enum Option<T> { + Some { value: T }, None } + pub enum Result<T, E> { + Ok { value: T }, + Err { error: E } } + pub enum List<T> { + Cons { head: T, + tail: Box<List<T>> }, + Nil } + pub enum Tree<T> { + Leaf { value: T }, + Node { left: Box<Tree<T>>, + right: Box<Tree<T>> } } - - - PAIR - type Pair<A, B> { - first: A; second: B } - - pub struct Pair<A, B> { - pub first: A, - pub second: B, - } - - @dataclass - class Pair(Generic[A,B]): - first: A; second: B - - type Pair[A,B any] struct{ - First A; Second B - } - - interface Pair<A, B> { - first: A; second: B; - } + + PYTHON + + @dataclass + class Pair(Generic[A,B]): + first: A; second: B + @dataclass + class Left(Generic[A]): value: A + class Right(Generic[B]): value: B + Sum = Union[Left[A], Right[B]] + @dataclass + class Some(Generic[T]): value: T + Option = Union[Some[T], None] + @dataclass + class Ok(Generic[T]): value: T + class Err(Generic[E]): error: E + Result = Union[Ok[T], Err[E]] + @dataclass + class Cons(Generic[T]): + head: T; tail: 'List[T]' + List = Union[Cons[T], None] + @dataclass + class Leaf(Generic[T]): value: T + class Node(Generic[T]): + - - - SUM - type Sum<A, B> = - Left{v:A} | Right{v:B} - - pub enum Sum<A, B> { - Left{v:A}, Right{v:B} - } - - Sum = Union[ - Left[A], Right[B] - ] - - type Sum[A,B any] interface{ - isSum() // + Left, Right - } - - type Sum<A, B> = - | { kind:'L', v:A } - | { kind:'R', v:B }; + + LOGIC — six small consumers - - - OPTION - type Option<T> = - Some{v:T} | None - - pub enum Option<T> { - Some{v:T}, None - } - - Option = Union[ - Some[T], _None - ] - - type Option[T any] interface{ - isOpt() // + Some, None_ - } - - type Option<T> = - | { kind:'Some', v:T } - | { kind:'None' }; + + .dag + + fn swap<A,B>(p: Pair<A,B>) + -> Pair<B,A> = + Pair{first:p.second,second:p.first} + fn unwrap_str( + s: Sum<Str,Str>) -> Str = + match s { + Left {value} => value + Right {value} => value } + fn or_else<T>( + o: Option<T>, d: T) -> T = + match o { + Some {value} => value + None => d } + fn unwrap_or<T,E>( + r: Result<T,E>, d: T) -> T = + match r { + Ok {value} => value + Err {_} => d } + fn length<T>(l: List<T>) -> Int = + match l { + Cons {tail,..} => 1 + length(tail) + Nil => 0 } + fn depth<T>(t: Tree<T>) -> Int = + match t { + Leaf {_} => 1 + Node {left,right} => + 1 + max(depth(left), + depth(right)) } - - - RESULT - type Result<T, E> = - Ok{v:T} | Err{e:E} - - pub enum Result<T, E> { - Ok{v:T}, Err{e:E} - } - - Result = Union[ - Ok[T], Err[E] - ] - - type Result[T,E any] interface{ - isResult() // + Ok, Err - } - - type Result<T, E> = - | { kind:'Ok', v:T } - | { kind:'Err', e:E }; + + RUST + + pub fn swap<A,B>(p: Pair<A,B>) -> Pair<B,A> { + Pair { first: p.second, second: p.first } + } + pub fn unwrap_str(s: Sum<String,String>) + -> String { + match s { + Sum::Left { value } => value, + Sum::Right { value } => value, + } + } + pub fn or_else<T>(o: Option<T>, d: T) -> T { + match o { + Option::Some { value } => value, + Option::None => d, + } + } + pub fn unwrap_or<T,E>(r: Result<T,E>, d: T) + -> T { + match r { + Result::Ok { value } => value, + Result::Err { error: _ } => d, + } + } + pub fn length<T>(l: List<T>) -> i64 { + match l { + List::Cons { tail, .. } => 1 + length(*tail), + List::Nil => 0, + } + } - - - LIST - type List<T> = - Cons{h:T,t:List<T>} | Nil - - pub enum List<T> { - Cons{h:T,t:Box<List<T>>}, - Nil } - - @dataclass - class Cons: h:T; t:'List[T]' - List = Union[Cons[T], Nil] - - type List[T any] interface{ - isList() // recursive Cons, - } // Nil_ - - type List<T> = - | { kind:'Cons', h:T, - t:List<T> } | {kind:'Nil'}; + + PYTHON + + def swap(p: Pair[A, B]) -> Pair[B, A]: + return Pair(first=p.second, + second=p.first) + def unwrap_str(s: Sum[str, str]) -> str: + match s: + case Left(value=v): return v + case Right(value=v): return v + def or_else(o: Option[T], d: T) -> T: + match o: + case Some(value=v): return v + case _: return d + def unwrap_or(r: Result[T, E], d: T) -> T: + match r: + case Ok(value=v): return v + case Err(): return d + def length(l: List[T]) -> int: + match l: + case Cons(tail=t): return 1 + length(t) + case _: return 0 + def depth(t: Tree[T]) -> int: + match t: + case Leaf(): return 1 + case Node(left=l, right=r): + return 1 + max(depth(l), depth(r)) - - - TREE - type Tree<T> = Leaf{v:T} - | Node{l:Tree,r:Tree} - - pub enum Tree<T> { - Leaf{v:T}, - Node{l:Box<..>, r:Box<..>} } - - @dataclass - class Leaf: v:T - Tree = Union[Leaf[T], Node[T]] - - type Tree[T any] interface{ - isTree() // Leaf, Node - } - - type Tree<T> = - | { kind:'Leaf', v:T } - | { kind:'Node', ... }; - - - - EITHER - type Either<L, R> = - Lhs{v:L} | Rhs{v:R} - - pub enum Either<L, R> { - Lhs{v:L}, Rhs{v:R} - } - - Either = Union[ - Lhs[L], Rhs[R] - ] - - type Either[L,R any] interface{ - isEither() // Lhs, Rhs - } - - type Either<L, R> = - | { kind:'Lhs', v:L } - | { kind:'Rhs', v:R }; - - - - NONEMPTY - type NonEmpty<T> { - head: T; tail: List<T> } - - pub struct NonEmpty<T> { - pub head: T, - pub tail: List<T>, - } - - @dataclass - class NonEmpty(Generic[T]): - head: T; tail: List[T] - - type NonEmpty[T any] struct{ - Head T; Tail List[T] - } - - interface NonEmpty<T> { - head: T; - tail: List<T>; - } - - A new type adds one row. A new language adds one column. The grid grows fast — your intent doesn't. + Two columns of source. Two columns of full working modules. The right grew with the language count and the type count. From 1f310c42681e882ad52f27b3fb982a2ce73f1e00 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 6 May 2026 19:58:36 +0000 Subject: [PATCH 03/16] =?UTF-8?q?docs:=20rework=20layout=20=E2=80=94=20log?= =?UTF-8?q?ic-left=20/=20generated-center=20/=20facts-right?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per review feedback: - Drop Python; single Rust target keeps the pictures simple. Swap-out comment in the closing punchline notes that any single target works. - Three-column layout: business logic on the left, generated Rust in the middle (visually dominant), facts (type defs) on the right. - Source on the wings stays compact while the centerpiece module grows with every type and consumer added. --- .../emission-intuition/01-start-small.svg | 94 +++--- .../emission-intuition/02-add-languages.svg | 127 ++++---- .../demos/emission-intuition/03-add-types.svg | 196 +++++------- .../demos/emission-intuition/04-now-scale.svg | 299 ++++++++---------- 4 files changed, 321 insertions(+), 395 deletions(-) diff --git a/docs/demos/emission-intuition/01-start-small.svg b/docs/demos/emission-intuition/01-start-small.svg index 3bcff4e9aee..0981228df32 100644 --- a/docs/demos/emission-intuition/01-start-small.svg +++ b/docs/demos/emission-intuition/01-start-small.svg @@ -1,13 +1,12 @@ - + Start small. - One type. One little function that uses it. One language. The compiler emits both. + One fact (a type). One small consumer of it. The compiler weaves them together into a working Rust module. - - TYPES — the compositional model + BUSINESS LOGIC + RUST — GENERATED + FACTS - + + .dag - - type Sum<A, B> = - Left { value: A } - | Right { value: B } - - - RUST - - pub enum Sum<A, B> { - Left { value: A }, - Right { value: B }, - } - - - LOGIC — someone actually calling it - - - .dag - - fn unwrap_str( - s: Sum<Str, Str> - ) -> Str = - match s { - Left { value } => value - Right { value } => value - } - - - RUST - - pub fn unwrap_str( - s: Sum<String, String>, - ) -> String { - match s { - Sum::Left { value } => value, - Sum::Right { value } => value, - } - } - - A type and one consumer of it. The compiler emits a working Rust module from the .dag. + + fn unwrap_str( + s: Sum<Str, Str> + ) -> Str = + match s { + Left { value } => value + Right { value } => value + } + + + + RUST + + pub enum Sum<A, B> { + Left { value: A }, + Right { value: B }, + } + pub fn unwrap_str( + s: Sum<String, String>, + ) -> String { + match s { + Sum::Left { value } => value, + Sum::Right { value } => value, + } + } + + + + .dag + + type Sum<A, B> = + Left { value: A } + | Right { value: B } + + Two thin columns of source flanking the working module the compiler produced. diff --git a/docs/demos/emission-intuition/02-add-languages.svg b/docs/demos/emission-intuition/02-add-languages.svg index a20856da341..0cccad0ca6a 100644 --- a/docs/demos/emission-intuition/02-add-languages.svg +++ b/docs/demos/emission-intuition/02-add-languages.svg @@ -1,83 +1,80 @@ - + - Add a language. - Same type, same consumer. Two languages — two working modules. + Add a fact and a consumer. + Two types and two functions in .dag — one Rust module that ties them together. - - TYPES — the compositional model + BUSINESS LOGIC + RUST — GENERATED + FACTS - + + .dag - - type Sum<A, B> = - Left { value: A } - | Right { value: B } + + fn swap<A, B>( + p: Pair<A, B> + ) -> Pair<B, A> = + Pair { + first: p.second, + second: p.first, + } + fn unwrap_str( + s: Sum<Str, Str> + ) -> Str = + match s { + Left { value } => value + Right { value } => value + } - - RUST - - pub enum Sum<A, B> { - Left { value: A }, - Right { value: B }, - } + + + RUST + + pub struct Pair<A, B> { + pub first: A, + pub second: B, + } + pub enum Sum<A, B> { + Left { value: A }, + Right { value: B }, + } + pub fn swap<A, B>( + p: Pair<A, B>, + ) -> Pair<B, A> { + Pair { + first: p.second, + second: p.first, + } + } + pub fn unwrap_str( + s: Sum<String, String>, + ) -> String { + match s { Sum::Left{value}=>value,Sum::Right{value}=>value } - - PYTHON - - @dataclass - class Left(Generic[A]): value: A - class Right(Generic[B]): value: B - Sum = Union[Left[A], Right[B]] + + + .dag + + type Pair<A, B> { + first: A + second: B + } + type Sum<A, B> = + Left { value: A } + | Right { value: B } - - LOGIC — someone actually calling it - - - .dag - - fn unwrap_str( - s: Sum<Str, Str> - ) -> Str = - match s { - Left { value } => value - Right { value } => value - } - - - RUST - - pub fn unwrap_str( - s: Sum<String, String>, - ) -> String { - match s { - Sum::Left { value } => value, - Sum::Right { value } => value, - } - } - - - PYTHON - - def unwrap_str( - s: Sum[str, str], - ) -> str: - match s: - case Left(value=v): return v - case Right(value=v): return v - - Source unchanged. Adding a language adds one column on the right — both rows fill in. + Source on the wings stayed compact. The middle holds a working module — types, functions, the lot. diff --git a/docs/demos/emission-intuition/03-add-types.svg b/docs/demos/emission-intuition/03-add-types.svg index f57e509711e..f5d6161f785 100644 --- a/docs/demos/emission-intuition/03-add-types.svg +++ b/docs/demos/emission-intuition/03-add-types.svg @@ -1,127 +1,105 @@ - + - Add types. - Three types, three small consumers. Same two languages — but each side has more to emit. + More facts. More consumers. + Four types, four small functions. Source on the wings grew a little — the module in the middle grew more. - - TYPES — three compositional models + BUSINESS LOGIC + RUST — GENERATED + FACTS - + + .dag - - type Pair<A, B> { - first: A; second: B - } - type Sum<A, B> = - Left { value: A } - | Right { value: B } - type Option<T> = - Some { value: T } - | None + + fn swap<A, B>(p: Pair<A, B>) + -> Pair<B, A> = + Pair { first: p.second, + second: p.first } + fn unwrap_str( + s: Sum<Str, Str>) -> Str = + match s { + Left { value } => value + Right { value } => value + } + fn or_else<T>( + o: Option<T>, d: T) -> T = + match o { + Some { value } => value + None => d + } + fn unwrap_or<T, E>( + r: Result<T, E>, d: T) -> T = + match r { + Ok { value } => value + Err { _ } => d + } - - RUST - - pub struct Pair<A, B> { - pub first: A, pub second: B, - } - pub enum Sum<A, B> { - Left { value: A }, - Right { value: B }, - } - pub enum Option<T> { - Some { value: T }, None, - } + + + RUST + + pub struct Pair<A, B> { pub first: A, pub second: B } + pub enum Sum<A, B> { + Left { value: A }, + Right { value: B }, + } + pub enum Option<T> { + Some { value: T }, + None, + } + pub enum Result<T, E> { + Ok { value: T }, + Err { error: E }, + } + pub fn swap<A, B>(p: Pair<A, B>) -> Pair<B, A> { + Pair { first: p.second, second: p.first } + } + pub fn unwrap_str(s: Sum<String, String>) -> String { + match s { + Sum::Left { value } => value, + Sum::Right { value } => value, + } + } + pub fn or_else<T>(o: Option<T>, d: T) -> T { + match o { Option::Some{value}=>value, Option::None=>d } + } + pub fn unwrap_or<T, E>(r: Result<T, E>, d: T) -> T { + match r { + Result::Ok { value } => value, + Result::Err { error: _ } => d, + } + } - - PYTHON - - @dataclass - class Pair(Generic[A,B]): - first: A; second: B - @dataclass - class Left(Generic[A]): value: A - class Right(Generic[B]): value: B - Sum = Union[Left[A], Right[B]] - @dataclass - class Some(Generic[T]): value: T - Option = Union[Some[T], None] + + + .dag + + type Pair<A, B> { + first: A + second: B + } + type Sum<A, B> = + Left { value: A } + | Right { value: B } + type Option<T> = + Some { value: T } + | None + type Result<T, E> = + Ok { value: T } + | Err { error: E } - - LOGIC — three small consumers - - - .dag - - fn swap<A, B>(p: Pair<A, B>) - -> Pair<B, A> = - Pair { first: p.second, - second: p.first } - fn unwrap_str( - s: Sum<Str, Str>) -> Str = - match s { - Left { value } => value - Right { value } => value - } - fn or_else<T>( - o: Option<T>, d: T) -> T = - match o { - Some { value } => value - None => d - } - - - RUST - - pub fn swap<A, B>(p: Pair<A, B>) - -> Pair<B, A> { - Pair { first: p.second, - second: p.first } - } - pub fn unwrap_str( - s: Sum<String, String>) -> String { - match s { - Sum::Left { value } => value, - Sum::Right { value } => value, - } - } - pub fn or_else<T>( - o: Option<T>, d: T) -> T { - match o { - Option::Some{value}=>value, - Option::None=>d } } - - - PYTHON - - def swap(p: Pair[A, B]) - -> Pair[B, A]: - return Pair(first=p.second, - second=p.first) - def unwrap_str( - s: Sum[str, str]) -> str: - match s: - case Left(value=v): return v - case Right(value=v): return v - def or_else( - o: Option[T], d: T) -> T: - match o: - case Some(value=v): return v - case None_(): return d - - Source grew by a few lines per type. The right side grew by a whole module per language. + Source on the wings is still scannable. The middle column is doing the heavy lifting. diff --git a/docs/demos/emission-intuition/04-now-scale.svg b/docs/demos/emission-intuition/04-now-scale.svg index e978a2bc253..b8ad5fae6c3 100644 --- a/docs/demos/emission-intuition/04-now-scale.svg +++ b/docs/demos/emission-intuition/04-now-scale.svg @@ -1,14 +1,13 @@ - + Now scale. - Six types and six small consumers. Same two languages. The right side is a working module per language — your source stayed compact. + Six types, six consumers. The wings still fit on a page. The middle is a real working module. - - TYPES — six compositional models + BUSINESS LOGIC + RUST — GENERATED + FACTS - + + .dag - - type Pair<A, B> { - first: A; second: B } - type Sum<A, B> = - Left { value: A } - | Right { value: B } - type Option<T> = - Some { value: T } | None - type Result<T, E> = - Ok { value: T } - | Err { error: E } - type List<T> = - Cons { head: T, - tail: List<T> } - | Nil - type Tree<T> = - Leaf { value: T } - | Node { left: Tree<T>, - right: Tree<T> } + + fn swap<A, B>(p: Pair<A, B>) + -> Pair<B, A> = + Pair { first: p.second, + second: p.first } + fn unwrap_str( + s: Sum<Str, Str>) -> Str = + match s { + Left { value } => value + Right { value } => value + } + fn or_else<T>( + o: Option<T>, d: T) -> T = + match o { + Some { value } => value + None => d + } + fn unwrap_or<T, E>( + r: Result<T, E>, d: T) -> T = + match r { + Ok { value } => value + Err { _ } => d + } + fn length<T>( + l: List<T>) -> Int = + match l { + Cons { tail, _ } => + 1 + length(tail) + Nil => 0 + } + fn depth<T>( + t: Tree<T>) -> Int = + match t { + Leaf { _ } => 1 + Node { left, right } => + 1 + max( + depth(left), + depth(right)) + } - - RUST - - pub struct Pair<A, B> { - pub first: A, pub second: B } - pub enum Sum<A, B> { - Left { value: A }, - Right { value: B } } - pub enum Option<T> { - Some { value: T }, None } - pub enum Result<T, E> { - Ok { value: T }, - Err { error: E } } - pub enum List<T> { - Cons { head: T, - tail: Box<List<T>> }, - Nil } - pub enum Tree<T> { - Leaf { value: T }, - Node { left: Box<Tree<T>>, - right: Box<Tree<T>> } } + + + RUST + + pub struct Pair<A, B> { pub first: A, pub second: B } + pub enum Sum<A, B> { + Left { value: A }, Right { value: B }, + } + pub enum Option<T> { Some { value: T }, None } + pub enum Result<T, E> { + Ok { value: T }, Err { error: E }, + } + pub enum List<T> { + Cons { head: T, tail: Box<List<T>> }, + Nil, + } + pub enum Tree<T> { + Leaf { value: T }, + Node { left: Box<Tree<T>>, + right: Box<Tree<T>> }, + } + pub fn swap<A, B>(p: Pair<A, B>) -> Pair<B, A> { + Pair { first: p.second, second: p.first } + } + pub fn unwrap_str(s: Sum<String, String>) -> String { + match s { + Sum::Left { value } => value, + Sum::Right { value } => value, + } + } + pub fn or_else<T>(o: Option<T>, d: T) -> T { + match o { Option::Some{value}=>value, Option::None=>d } + } + pub fn unwrap_or<T, E>(r: Result<T, E>, d: T) -> T { + match r { + Result::Ok { value } => value, + Result::Err { error: _ } => d, + } + } + pub fn length<T>(l: List<T>) -> i64 { + match l { + List::Cons { tail, .. } => 1 + length(*tail), + List::Nil => 0, + } + } + pub fn depth<T>(t: Tree<T>) -> i64 { + match t { + Tree::Leaf { .. } => 1, + Tree::Node { left, right } => + 1 + std::cmp::max(depth(left), depth(right)), + } + } - - PYTHON - - @dataclass - class Pair(Generic[A,B]): - first: A; second: B - @dataclass - class Left(Generic[A]): value: A - class Right(Generic[B]): value: B - Sum = Union[Left[A], Right[B]] - @dataclass - class Some(Generic[T]): value: T - Option = Union[Some[T], None] - @dataclass - class Ok(Generic[T]): value: T - class Err(Generic[E]): error: E - Result = Union[Ok[T], Err[E]] - @dataclass - class Cons(Generic[T]): - head: T; tail: 'List[T]' - List = Union[Cons[T], None] - @dataclass - class Leaf(Generic[T]): value: T - class Node(Generic[T]): - + + + .dag + + type Pair<A, B> { + first: A; second: B + } + type Sum<A, B> = + Left { value: A } + | Right { value: B } + type Option<T> = + Some { value: T } + | None + type Result<T, E> = + Ok { value: T } + | Err { error: E } + type List<T> = + Cons { head: T, + tail: List<T> } + | Nil + type Tree<T> = + Leaf { value: T } + | Node { left: Tree<T>, + right: Tree<T> } - - LOGIC — six small consumers - - - .dag - - fn swap<A,B>(p: Pair<A,B>) - -> Pair<B,A> = - Pair{first:p.second,second:p.first} - fn unwrap_str( - s: Sum<Str,Str>) -> Str = - match s { - Left {value} => value - Right {value} => value } - fn or_else<T>( - o: Option<T>, d: T) -> T = - match o { - Some {value} => value - None => d } - fn unwrap_or<T,E>( - r: Result<T,E>, d: T) -> T = - match r { - Ok {value} => value - Err {_} => d } - fn length<T>(l: List<T>) -> Int = - match l { - Cons {tail,..} => 1 + length(tail) - Nil => 0 } - fn depth<T>(t: Tree<T>) -> Int = - match t { - Leaf {_} => 1 - Node {left,right} => - 1 + max(depth(left), - depth(right)) } - - - RUST - - pub fn swap<A,B>(p: Pair<A,B>) -> Pair<B,A> { - Pair { first: p.second, second: p.first } - } - pub fn unwrap_str(s: Sum<String,String>) - -> String { - match s { - Sum::Left { value } => value, - Sum::Right { value } => value, - } - } - pub fn or_else<T>(o: Option<T>, d: T) -> T { - match o { - Option::Some { value } => value, - Option::None => d, - } - } - pub fn unwrap_or<T,E>(r: Result<T,E>, d: T) - -> T { - match r { - Result::Ok { value } => value, - Result::Err { error: _ } => d, - } - } - pub fn length<T>(l: List<T>) -> i64 { - match l { - List::Cons { tail, .. } => 1 + length(*tail), - List::Nil => 0, - } - } - - - PYTHON - - def swap(p: Pair[A, B]) -> Pair[B, A]: - return Pair(first=p.second, - second=p.first) - def unwrap_str(s: Sum[str, str]) -> str: - match s: - case Left(value=v): return v - case Right(value=v): return v - def or_else(o: Option[T], d: T) -> T: - match o: - case Some(value=v): return v - case _: return d - def unwrap_or(r: Result[T, E], d: T) -> T: - match r: - case Ok(value=v): return v - case Err(): return d - def length(l: List[T]) -> int: - match l: - case Cons(tail=t): return 1 + length(t) - case _: return 0 - def depth(t: Tree[T]) -> int: - match t: - case Leaf(): return 1 - case Node(left=l, right=r): - return 1 + max(depth(l), depth(r)) - - Two columns of source. Two columns of full working modules. The right grew with the language count and the type count. + A few inches of source on each wing. A real Rust module in the middle. Swap "Rust" for any target — the wings don't change. From 0a0c8f6c0431423a0205fa81e4f3dc78b82e49ed Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 6 May 2026 20:03:41 +0000 Subject: [PATCH 04/16] docs: beef up emission to make the asymmetry pop Per review: show how the centerpiece compounds as facts/logic layer in while source on the wings stays trivial. Now each type emits a derive line + the type def + a full impl block with constructors and predicates; each consumer is rendered as a real Rust fn body. Source stays compact; the middle keeps growing. Approximate line counts: 01: wings ~9 middle ~18 02: wings ~18 middle ~36 03: wings ~30 middle ~80 04: wings ~50 middle ~170 Closing line on each diagram describes what one more fact costs you on the wings vs. what you get in the middle. --- .../emission-intuition/01-start-small.svg | 53 ++--- .../emission-intuition/02-add-languages.svg | 99 ++++----- .../demos/emission-intuition/03-add-types.svg | 127 +++++++----- .../demos/emission-intuition/04-now-scale.svg | 192 +++++++++++------- 4 files changed, 266 insertions(+), 205 deletions(-) diff --git a/docs/demos/emission-intuition/01-start-small.svg b/docs/demos/emission-intuition/01-start-small.svg index 0981228df32..3cea1b4a53e 100644 --- a/docs/demos/emission-intuition/01-start-small.svg +++ b/docs/demos/emission-intuition/01-start-small.svg @@ -1,4 +1,4 @@ - + Start small. - One fact (a type). One small consumer of it. The compiler weaves them together into a working Rust module. + One fact. One small consumer. Watch what comes out the middle. - BUSINESS LOGIC - RUST — GENERATED - FACTS + BUSINESS LOGIC + RUST — GENERATED + FACTS - + .dag @@ -32,30 +32,33 @@ Right { value } => value } - + RUST - - pub enum Sum<A, B> { - Left { value: A }, - Right { value: B }, - } - pub fn unwrap_str( - s: Sum<String, String>, - ) -> String { - match s { - Sum::Left { value } => value, - Sum::Right { value } => value, - } - } + + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub enum Sum<A, B> { + Left { value: A }, + Right { value: B }, + } + impl<A, B> Sum<A, B> { + pub fn left(value: A) -> Self { Self::Left { value } } + pub fn right(value: B) -> Self { Self::Right { value } } + pub fn is_left(&self) -> bool { matches!(self, Self::Left {..}) } + pub fn is_right(&self) -> bool { matches!(self, Self::Right {..}) } + } + pub fn unwrap_str(s: Sum<String, String>) -> String { + match s { + Sum::Left { value } => value, + Sum::Right { value } => value, + - + .dag - + type Sum<A, B> = - Left { value: A } - | Right { value: B } + Left{value:A} | Right{value:B} - Two thin columns of source flanking the working module the compiler produced. + ~9 lines on the wings. ~18 lines in the middle — derives, constructors, predicates, the function body. All synthesized from the structural fold. diff --git a/docs/demos/emission-intuition/02-add-languages.svg b/docs/demos/emission-intuition/02-add-languages.svg index 0cccad0ca6a..39be7b4d6d1 100644 --- a/docs/demos/emission-intuition/02-add-languages.svg +++ b/docs/demos/emission-intuition/02-add-languages.svg @@ -1,4 +1,4 @@ - + - Add a fact and a consumer. - Two types and two functions in .dag — one Rust module that ties them together. + Layer in another fact and consumer. + Source on the wings barely moved. The middle doubled. - BUSINESS LOGIC - RUST — GENERATED - FACTS + BUSINESS LOGIC + RUST — GENERATED + FACTS - + .dag - + fn swap<A, B>( p: Pair<A, B> ) -> Pair<B, A> = - Pair { - first: p.second, - second: p.first, - } - fn unwrap_str( - s: Sum<Str, Str> - ) -> Str = - match s { - Left { value } => value - Right { value } => value - } + Pair { first: p.second, + second: p.first } + fn unwrap_str( + s: Sum<Str, Str> + ) -> Str = + match s { + Left { value } => value + Right { value } => value + } - + RUST - - pub struct Pair<A, B> { - pub first: A, - pub second: B, - } - pub enum Sum<A, B> { - Left { value: A }, - Right { value: B }, - } - pub fn swap<A, B>( - p: Pair<A, B>, - ) -> Pair<B, A> { - Pair { - first: p.second, - second: p.first, - } + + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub struct Pair<A, B> { pub first: A, pub second: B } + impl<A, B> Pair<A, B> { + pub fn new(first: A, second: B) -> Self { Self { first, second } } + } + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub enum Sum<A, B> { + Left { value: A }, + Right { value: B }, + } + impl<A, B> Sum<A, B> { + pub fn left(value: A) -> Self { Self::Left { value } } + pub fn right(value: B) -> Self { Self::Right { value } } + pub fn is_left(&self) -> bool { matches!(self, Self::Left {..}) } + pub fn is_right(&self) -> bool { matches!(self, Self::Right {..}) } } - pub fn unwrap_str( - s: Sum<String, String>, - ) -> String { - match s { Sum::Left{value}=>value,Sum::Right{value}=>value } + pub fn swap<A, B>(p: Pair<A, B>) -> Pair<B, A> { + Pair { first: p.second, second: p.first } + } + pub fn unwrap_str(s: Sum<String, String>) -> String { + match s { + Sum::Left { value } => value, + Sum::Right { value } => value, + } + } - + .dag - + type Pair<A, B> { - first: A - second: B - } - type Sum<A, B> = - Left { value: A } - | Right { value: B } + first: A; second: B + } + type Sum<A, B> = + Left{value:A}|Right{value:B} - Source on the wings stayed compact. The middle holds a working module — types, functions, the lot. + Wings: ~18 lines. Middle: ~36 lines. Each fact you add costs you 2-3 lines and nets you a working impl block of derives, constructors, and predicates. diff --git a/docs/demos/emission-intuition/03-add-types.svg b/docs/demos/emission-intuition/03-add-types.svg index f5d6161f785..c78a51e5053 100644 --- a/docs/demos/emission-intuition/03-add-types.svg +++ b/docs/demos/emission-intuition/03-add-types.svg @@ -1,4 +1,4 @@ - + - More facts. More consumers. - Four types, four small functions. Source on the wings grew a little — the module in the middle grew more. + Layer in two more. + Source on the wings still scans on a phone. The middle has tripled. - BUSINESS LOGIC - RUST — GENERATED - FACTS + BUSINESS LOGIC + RUST — GENERATED + FACTS - + .dag - + fn swap<A, B>(p: Pair<A, B>) -> Pair<B, A> = Pair { first: p.second, @@ -47,59 +47,78 @@ Err { _ } => d } - + RUST - - pub struct Pair<A, B> { pub first: A, pub second: B } - pub enum Sum<A, B> { - Left { value: A }, - Right { value: B }, + + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub struct Pair<A, B> { pub first: A, pub second: B } + impl<A, B> Pair<A, B> { + pub fn new(first: A, second: B) -> Self { Self { first, second } } } - pub enum Option<T> { - Some { value: T }, - None, + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub enum Sum<A, B> { + Left { value: A }, Right { value: B }, } - pub enum Result<T, E> { - Ok { value: T }, - Err { error: E }, - } - pub fn swap<A, B>(p: Pair<A, B>) -> Pair<B, A> { - Pair { first: p.second, second: p.first } - } - pub fn unwrap_str(s: Sum<String, String>) -> String { - match s { - Sum::Left { value } => value, - Sum::Right { value } => value, - } - } - pub fn or_else<T>(o: Option<T>, d: T) -> T { - match o { Option::Some{value}=>value, Option::None=>d } - } - pub fn unwrap_or<T, E>(r: Result<T, E>, d: T) -> T { - match r { - Result::Ok { value } => value, - Result::Err { error: _ } => d, - } - } + impl<A, B> Sum<A, B> { + pub fn left(value: A) -> Self { Self::Left { value } } + pub fn right(value: B) -> Self { Self::Right { value } } + pub fn is_left(&self) -> bool { matches!(self, Self::Left {..}) } + pub fn is_right(&self) -> bool { matches!(self, Self::Right {..}) } + } + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub enum Option<T> { Some { value: T }, None } + impl<T> Option<T> { + pub fn some(value: T) -> Self { Self::Some { value } } + pub fn none() -> Self { Self::None } + pub fn is_some(&self) -> bool { matches!(self, Self::Some {..}) } + pub fn is_none(&self) -> bool { matches!(self, Self::None) } + } + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub enum Result<T, E> { + Ok { value: T }, Err { error: E }, + } + impl<T, E> Result<T, E> { + pub fn ok(value: T) -> Self { Self::Ok { value } } + pub fn err(error: E) -> Self { Self::Err { error } } + pub fn is_ok(&self) -> bool { matches!(self, Self::Ok {..}) } + pub fn is_err(&self) -> bool { matches!(self, Self::Err {..}) } + } + pub fn swap<A, B>(p: Pair<A, B>) -> Pair<B, A> { + Pair { first: p.second, second: p.first } + } + pub fn unwrap_str(s: Sum<String, String>) -> String { + match s { + Sum::Left { value } => value, + Sum::Right { value } => value, + } + } + pub fn or_else<T>(o: Option<T>, d: T) -> T { + match o { + Option::Some { value } => value, + Option::None => d, + } + } + pub fn unwrap_or<T, E>(r: Result<T, E>, d: T) -> T { + match r { + Result::Ok { value } => value, + Result::Err { error: _ } => d, + } + } - + .dag - + type Pair<A, B> { - first: A - second: B - } - type Sum<A, B> = - Left { value: A } - | Right { value: B } - type Option<T> = - Some { value: T } - | None - type Result<T, E> = - Ok { value: T } - | Err { error: E } + first: A; second: B + } + type Sum<A, B> = + Left{value:A}|Right{value:B} + type Option<T> = + Some{value:T} | None + type Result<T, E> = + Ok{value:T} | Err{error:E} - Source on the wings is still scannable. The middle column is doing the heavy lifting. + Wings: ~30 lines. Middle: ~80+ lines. Every fact you add brings its full impl block with it for free. diff --git a/docs/demos/emission-intuition/04-now-scale.svg b/docs/demos/emission-intuition/04-now-scale.svg index b8ad5fae6c3..a40066ab39f 100644 --- a/docs/demos/emission-intuition/04-now-scale.svg +++ b/docs/demos/emission-intuition/04-now-scale.svg @@ -1,4 +1,4 @@ - + - Now scale. - Six types, six consumers. The wings still fit on a page. The middle is a real working module. + Layer in two recursive ones. + Six facts, six functions. Compact source. The middle keeps growing. - BUSINESS LOGIC - RUST — GENERATED - FACTS + BUSINESS LOGIC + RUST — GENERATED + FACTS - + .dag - + fn swap<A, B>(p: Pair<A, B>) -> Pair<B, A> = Pair { first: p.second, @@ -33,7 +33,7 @@ s: Sum<Str, Str>) -> Str = match s { Left { value } => value - Right { value } => value + Right { value } => value } fn or_else<T>( o: Option<T>, d: T) -> T = @@ -64,60 +64,101 @@ depth(right)) } - + RUST - - pub struct Pair<A, B> { pub first: A, pub second: B } - pub enum Sum<A, B> { - Left { value: A }, Right { value: B }, - } - pub enum Option<T> { Some { value: T }, None } - pub enum Result<T, E> { - Ok { value: T }, Err { error: E }, - } - pub enum List<T> { - Cons { head: T, tail: Box<List<T>> }, - Nil, - } - pub enum Tree<T> { - Leaf { value: T }, - Node { left: Box<Tree<T>>, - right: Box<Tree<T>> }, - } - pub fn swap<A, B>(p: Pair<A, B>) -> Pair<B, A> { - Pair { first: p.second, second: p.first } - } - pub fn unwrap_str(s: Sum<String, String>) -> String { - match s { - Sum::Left { value } => value, - Sum::Right { value } => value, - } - } - pub fn or_else<T>(o: Option<T>, d: T) -> T { - match o { Option::Some{value}=>value, Option::None=>d } - } - pub fn unwrap_or<T, E>(r: Result<T, E>, d: T) -> T { - match r { - Result::Ok { value } => value, - Result::Err { error: _ } => d, - } - } - pub fn length<T>(l: List<T>) -> i64 { - match l { - List::Cons { tail, .. } => 1 + length(*tail), - List::Nil => 0, - } - } - pub fn depth<T>(t: Tree<T>) -> i64 { - match t { - Tree::Leaf { .. } => 1, - Tree::Node { left, right } => - 1 + std::cmp::max(depth(left), depth(right)), - } - } + + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub struct Pair<A, B> { pub first: A, pub second: B } + impl<A, B> Pair<A, B> { + pub fn new(first: A, second: B) -> Self { Self { first, second } } + } + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub enum Sum<A, B> { Left { value: A }, Right { value: B } } + impl<A, B> Sum<A, B> { + pub fn left(value: A) -> Self { Self::Left { value } } + pub fn right(value: B) -> Self { Self::Right { value } } + pub fn is_left(&self) -> bool { matches!(self, Self::Left {..}) } + pub fn is_right(&self) -> bool { matches!(self, Self::Right {..}) } + } + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub enum Option<T> { Some { value: T }, None } + impl<T> Option<T> { + pub fn some(value: T) -> Self { Self::Some { value } } + pub fn none() -> Self { Self::None } + pub fn is_some(&self) -> bool { matches!(self, Self::Some {..}) } + pub fn is_none(&self) -> bool { matches!(self, Self::None) } + } + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub enum Result<T, E> { Ok { value: T }, Err { error: E } } + impl<T, E> Result<T, E> { + pub fn ok(value: T) -> Self { Self::Ok { value } } + pub fn err(error: E) -> Self { Self::Err { error } } + pub fn is_ok(&self) -> bool { matches!(self, Self::Ok {..}) } + pub fn is_err(&self) -> bool { matches!(self, Self::Err {..}) } + } + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub enum List<T> { + Cons { head: T, tail: Box<List<T>> }, + Nil, + } + impl<T> List<T> { + pub fn cons(head: T, tail: List<T>) -> Self { + Self::Cons { head, tail: Box::new(tail) } + } + pub fn nil() -> Self { Self::Nil } + pub fn is_cons(&self) -> bool { matches!(self, Self::Cons {..}) } + pub fn is_nil(&self) -> bool { matches!(self, Self::Nil) } + } + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub enum Tree<T> { + Leaf { value: T }, + Node { left: Box<Tree<T>>, right: Box<Tree<T>> }, + } + impl<T> Tree<T> { + pub fn leaf(value: T) -> Self { Self::Leaf { value } } + pub fn node(left: Tree<T>, right: Tree<T>) -> Self { + Self::Node { left: Box::new(left), right: Box::new(right) } + } + pub fn is_leaf(&self) -> bool { matches!(self, Self::Leaf {..}) } + pub fn is_node(&self) -> bool { matches!(self, Self::Node {..}) } + } + pub fn swap<A, B>(p: Pair<A, B>) -> Pair<B, A> { + Pair { first: p.second, second: p.first } + } + pub fn unwrap_str(s: Sum<String, String>) -> String { + match s { + Sum::Left { value } => value, + Sum::Right { value } => value, + } + } + pub fn or_else<T>(o: Option<T>, d: T) -> T { + match o { + Option::Some { value } => value, + Option::None => d, + } + } + pub fn unwrap_or<T, E>(r: Result<T, E>, d: T) -> T { + match r { + Result::Ok { value } => value, + Result::Err { error: _ } => d, + } + } + pub fn length<T>(l: &List<T>) -> i64 { + match l { + List::Cons { tail, .. } => 1 + length(tail), + List::Nil => 0, + } + } + pub fn depth<T>(t: &Tree<T>) -> i64 { + match t { + Tree::Leaf { .. } => 1, + Tree::Node { left, right } => + 1 + std::cmp::max(depth(left), depth(right)), + } + } - + .dag @@ -125,22 +166,19 @@ first: A; second: B } type Sum<A, B> = - Left { value: A } - | Right { value: B } - type Option<T> = - Some { value: T } - | None - type Result<T, E> = - Ok { value: T } - | Err { error: E } - type List<T> = - Cons { head: T, - tail: List<T> } - | Nil - type Tree<T> = - Leaf { value: T } - | Node { left: Tree<T>, - right: Tree<T> } + Left{value:A}|Right{value:B} + type Option<T> = + Some{value:T} | None + type Result<T, E> = + Ok{value:T} | Err{error:E} + type List<T> = + Cons { head: T, + tail: List<T> } + | Nil + type Tree<T> = + Leaf { value: T } + | Node { left: Tree<T>, + right: Tree<T> } - A few inches of source on each wing. A real Rust module in the middle. Swap "Rust" for any target — the wings don't change. + Wings: ~50 lines. Middle: ~170 lines. Add another fact — wings grow by 3, middle grows by 15. The source stays trivial to maintain; the centerpiece is what runs. From e5108c2c0b13d8da47445ef89cc5075094a68c0a Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 6 May 2026 20:08:45 +0000 Subject: [PATCH 05/16] =?UTF-8?q?docs:=20pivot=20story=20=E2=80=94=20one?= =?UTF-8?q?=20Sum=20primitive,=20layered=20consumers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per review: stick to a single simple type (Sum) and show how it propagates through layers — primitive consumer → helpers → library → app. The FACTS column stays small across all four diagrams; LOGIC grows in clearly labeled layers (// primitive consumer, // helpers, // library, // app); the middle compounds because every layer instantiates Sum at new concrete types. Diagram 1: Sum + 1 consumer (3 lines of facts) Diagram 2: + 2 generic helpers (FACTS UNCHANGED) Diagram 3: + library functions (FACTS GREW BY ONE LINE — Result alias) Diagram 4: + app handler (SAME 5 LINES OF FACTS) Adds a "FACTS UNCHANGED / SAME N LINES" pill to each diagram so the reader can see the right column visually anchored. --- .../emission-intuition/01-start-small.svg | 42 ++- .../emission-intuition/02-add-languages.svg | 116 ++++---- .../demos/emission-intuition/03-add-types.svg | 179 ++++++------ .../demos/emission-intuition/04-now-scale.svg | 263 ++++++++---------- 4 files changed, 278 insertions(+), 322 deletions(-) diff --git a/docs/demos/emission-intuition/01-start-small.svg b/docs/demos/emission-intuition/01-start-small.svg index 3cea1b4a53e..72346268a5d 100644 --- a/docs/demos/emission-intuition/01-start-small.svg +++ b/docs/demos/emission-intuition/01-start-small.svg @@ -1,4 +1,4 @@ - + - Start small. - One fact. One small consumer. Watch what comes out the middle. + One primitive. + A single Sum type. One function that uses it. The starting point. BUSINESS LOGIC RUST — GENERATED FACTS - + .dag - - fn unwrap_str( - s: Sum<Str, Str> - ) -> Str = - match s { - Left { value } => value - Right { value } => value - } + + // primitive consumer + fn unwrap_str( + s: Sum<Str, Str> + ) -> Str = + match s { + Left { value } => value + Right { value } => value + } - + RUST - + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Sum<A, B> { Left { value: A }, @@ -47,18 +49,14 @@ pub fn is_left(&self) -> bool { matches!(self, Self::Left {..}) } pub fn is_right(&self) -> bool { matches!(self, Self::Right {..}) } } - pub fn unwrap_str(s: Sum<String, String>) -> String { - match s { - Sum::Left { value } => value, - Sum::Right { value } => value, - + pub fn unwrap_str(s: Sum<String,String>) -> String { ... } - + .dag type Sum<A, B> = Left{value:A} | Right{value:B} - ~9 lines on the wings. ~18 lines in the middle — derives, constructors, predicates, the function body. All synthesized from the structural fold. + 3 lines of facts. 7 lines of logic. ~13 lines of Rust. The fact will not grow as we add layers. diff --git a/docs/demos/emission-intuition/02-add-languages.svg b/docs/demos/emission-intuition/02-add-languages.svg index 39be7b4d6d1..a02e4875337 100644 --- a/docs/demos/emission-intuition/02-add-languages.svg +++ b/docs/demos/emission-intuition/02-add-languages.svg @@ -1,4 +1,4 @@ - + - Layer in another fact and consumer. - Source on the wings barely moved. The middle doubled. + Helpers built on it. + Same Sum on the right. Two generic combinators on the left. The middle holds Sum's emission and three function bodies. BUSINESS LOGIC RUST — GENERATED FACTS - + .dag - - fn swap<A, B>( - p: Pair<A, B> - ) -> Pair<B, A> = - Pair { first: p.second, - second: p.first } - fn unwrap_str( - s: Sum<Str, Str> - ) -> Str = - match s { - Left { value } => value - Right { value } => value - } + + // primitive consumer + fn unwrap_str( + s: Sum<Str, Str>) -> Str = + match s { + Left { value } => value + Right { value } => value + } + // helpers (generic over Sum) + fn map<A, B, C>( + s: Sum<A, B>, + f: A -> C + ) -> Sum<C, B> = + match s { + Left { value } => + Left { value: f(value) } + Right { value } => Right { value } + } + fn and_then<A, B, C>( + s: Sum<A, B>, + f: A -> Sum<C, B> + ) -> Sum<C, B> = + match s { + Left { value } => f(value) + Right { value } => Right { value } } - + RUST - + #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub struct Pair<A, B> { pub first: A, pub second: B } - impl<A, B> Pair<A, B> { - pub fn new(first: A, second: B) -> Self { Self { first, second } } - } - #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub enum Sum<A, B> { - Left { value: A }, - Right { value: B }, - } - impl<A, B> Sum<A, B> { - pub fn left(value: A) -> Self { Self::Left { value } } - pub fn right(value: B) -> Self { Self::Right { value } } - pub fn is_left(&self) -> bool { matches!(self, Self::Left {..}) } - pub fn is_right(&self) -> bool { matches!(self, Self::Right {..}) } - } - pub fn swap<A, B>(p: Pair<A, B>) -> Pair<B, A> { - Pair { first: p.second, second: p.first } - } - pub fn unwrap_str(s: Sum<String, String>) -> String { - match s { - Sum::Left { value } => value, - Sum::Right { value } => value, - } - } + pub enum Sum<A, B> { Left { value: A }, Right { value: B } } + impl<A, B> Sum<A, B> { + pub fn left(value: A) -> Self { Self::Left { value } } + pub fn right(value: B) -> Self { Self::Right { value } } + pub fn is_left(&self) -> bool { matches!(self, Self::Left {..}) } + pub fn is_right(&self) -> bool { matches!(self, Self::Right {..}) } + } + pub fn unwrap_str(s: Sum<String, String>) -> String { + match s { + Sum::Left { value } => value, + Sum::Right { value } => value, + } + } + pub fn map<A, B, C>(s: Sum<A, B>, f: impl FnOnce(A) -> C) + -> Sum<C, B> { + match s { + Sum::Left { value } => Sum::Left { value: f(value) }, + Sum::Right { value } => Sum::Right { value }, + } + } + pub fn and_then<A, B, C>(s: Sum<A, B>, + f: impl FnOnce(A) -> Sum<C, B>) -> Sum<C, B> { ... } - + .dag - - type Pair<A, B> { - first: A; second: B - } - type Sum<A, B> = - Left{value:A}|Right{value:B} + + type Sum<A, B> = + Left{value:A} | Right{value:B} + + FACTS UNCHANGED - Wings: ~18 lines. Middle: ~36 lines. Each fact you add costs you 2-3 lines and nets you a working impl block of derives, constructors, and predicates. + The fact didn't move. The logic added a couple of helpers. The middle expanded by Sum's full impl block plus three Rust function bodies. diff --git a/docs/demos/emission-intuition/03-add-types.svg b/docs/demos/emission-intuition/03-add-types.svg index c78a51e5053..a3e08e7fa11 100644 --- a/docs/demos/emission-intuition/03-add-types.svg +++ b/docs/demos/emission-intuition/03-add-types.svg @@ -1,4 +1,4 @@ - + - Layer in two more. - Source on the wings still scans on a phone. The middle has tripled. + A library built from those helpers. + Same Sum on the right (with one alias). Library functions on the left use Sum at concrete types like Int and HttpError. BUSINESS LOGIC RUST — GENERATED FACTS - + .dag - - fn swap<A, B>(p: Pair<A, B>) - -> Pair<B, A> = - Pair { first: p.second, - second: p.first } - fn unwrap_str( - s: Sum<Str, Str>) -> Str = - match s { - Left { value } => value - Right { value } => value - } - fn or_else<T>( - o: Option<T>, d: T) -> T = - match o { - Some { value } => value - None => d - } - fn unwrap_or<T, E>( - r: Result<T, E>, d: T) -> T = - match r { - Ok { value } => value - Err { _ } => d - } + + // primitive consumer + fn unwrap_str( + s: Sum<Str, Str>) -> Str = + match s { + Left { value } => value + Right { value } => value } + // helpers + fn map<A,B,C>( + s: Sum<A,B>, f: A->C) -> Sum<C,B> = + match s { + Left { value } => Left { value: f(value) } + Right { value } => Right { value } } + fn and_then<A,B,C>( + s: Sum<A,B>, f: A->Sum<C,B>) -> Sum<C,B> = + match s { + Left { value } => f(value) + Right { value } => Right { value } } + // library — Sum with concrete primitives + fn parse_int( + s: Str + ) -> Sum<ParseError, Int> = + // ... try parse, return Left on err + + fn http_get( + url: Str + ) -> Sum<HttpError, Bytes> = + // ... fetch, return Left on err + fn validate_email( + s: Str + ) -> Sum<ValidationError, Email> = + // ... regex check etc. - + RUST - + #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub struct Pair<A, B> { pub first: A, pub second: B } - impl<A, B> Pair<A, B> { - pub fn new(first: A, second: B) -> Self { Self { first, second } } - } - #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub enum Sum<A, B> { - Left { value: A }, Right { value: B }, - } - impl<A, B> Sum<A, B> { - pub fn left(value: A) -> Self { Self::Left { value } } - pub fn right(value: B) -> Self { Self::Right { value } } - pub fn is_left(&self) -> bool { matches!(self, Self::Left {..}) } - pub fn is_right(&self) -> bool { matches!(self, Self::Right {..}) } - } - #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub enum Option<T> { Some { value: T }, None } - impl<T> Option<T> { - pub fn some(value: T) -> Self { Self::Some { value } } - pub fn none() -> Self { Self::None } - pub fn is_some(&self) -> bool { matches!(self, Self::Some {..}) } - pub fn is_none(&self) -> bool { matches!(self, Self::None) } + pub enum Sum<A, B> { Left { value: A }, Right { value: B } } + impl<A, B> Sum<A, B> { + pub fn left(value: A) -> Self { Self::Left { value } } + pub fn right(value: B) -> Self { Self::Right { value } } + pub fn is_left(&self) -> bool { matches!(self, Self::Left {..}) } + pub fn is_right(&self) -> bool { matches!(self, Self::Right {..}) } + } + // alias from .dag + pub type Result<T, E> = Sum<E, T>; + pub fn unwrap_str(s: Sum<String, String>) -> String { + match s { + Sum::Left { value } => value, + Sum::Right { value } => value, + } + } + pub fn map<A, B, C>(s: Sum<A, B>, f: impl FnOnce(A) -> C) + -> Sum<C, B> { + match s { + Sum::Left { value } => Sum::Left { value: f(value) }, + Sum::Right { value } => Sum::Right { value }, + } } - #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub enum Result<T, E> { - Ok { value: T }, Err { error: E }, - } - impl<T, E> Result<T, E> { - pub fn ok(value: T) -> Self { Self::Ok { value } } - pub fn err(error: E) -> Self { Self::Err { error } } - pub fn is_ok(&self) -> bool { matches!(self, Self::Ok {..}) } - pub fn is_err(&self) -> bool { matches!(self, Self::Err {..}) } - } - pub fn swap<A, B>(p: Pair<A, B>) -> Pair<B, A> { - Pair { first: p.second, second: p.first } - } - pub fn unwrap_str(s: Sum<String, String>) -> String { - match s { - Sum::Left { value } => value, - Sum::Right { value } => value, - } - } - pub fn or_else<T>(o: Option<T>, d: T) -> T { - match o { - Option::Some { value } => value, - Option::None => d, - } - } - pub fn unwrap_or<T, E>(r: Result<T, E>, d: T) -> T { - match r { - Result::Ok { value } => value, - Result::Err { error: _ } => d, - } - } + pub fn and_then<A, B, C>(s: Sum<A, B>, + f: impl FnOnce(A) -> Sum<C, B>) -> Sum<C, B> { + match s { + Sum::Left { value } => f(value), + Sum::Right { value } => Sum::Right { value }, + } + } + pub fn parse_int(s: &str) -> Sum<ParseError, i64> { + s.parse::<i64>().map_or_else( + |e| Sum::Left { value: ParseError::from(e) }, + |n| Sum::Right { value: n }) + } + pub fn http_get(url: &str) -> Sum<HttpError, Bytes> { ... } + pub fn validate_email(s: &str) -> Sum<ValidationError, Email> { ... } - + .dag - - type Pair<A, B> { - first: A; second: B - } - type Sum<A, B> = - Left{value:A}|Right{value:B} - type Option<T> = - Some{value:T} | None - type Result<T, E> = - Ok{value:T} | Err{error:E} + + type Sum<A, B> = + Left{value:A} | Right{value:B} + type Result<T, E> = Sum<E, T> + + FACTS GREW BY ONE LINE - Wings: ~30 lines. Middle: ~80+ lines. Every fact you add brings its full impl block with it for free. + Five lines of facts. The library on the left compounded — and the middle compounded faster. diff --git a/docs/demos/emission-intuition/04-now-scale.svg b/docs/demos/emission-intuition/04-now-scale.svg index a40066ab39f..b56378be0cd 100644 --- a/docs/demos/emission-intuition/04-now-scale.svg +++ b/docs/demos/emission-intuition/04-now-scale.svg @@ -1,4 +1,4 @@ - + - Layer in two recursive ones. - Six facts, six functions. Compact source. The middle keeps growing. + An app uses the library. + Same Sum on the right. App handler on the left composes library functions through the helpers. The middle is the real Rust module. BUSINESS LOGIC RUST — GENERATED FACTS - + .dag - - fn swap<A, B>(p: Pair<A, B>) - -> Pair<B, A> = - Pair { first: p.second, - second: p.first } - fn unwrap_str( - s: Sum<Str, Str>) -> Str = - match s { - Left { value } => value - Right { value } => value - } - fn or_else<T>( - o: Option<T>, d: T) -> T = - match o { - Some { value } => value - None => d - } - fn unwrap_or<T, E>( - r: Result<T, E>, d: T) -> T = - match r { - Ok { value } => value - Err { _ } => d - } - fn length<T>( - l: List<T>) -> Int = - match l { - Cons { tail, _ } => - 1 + length(tail) - Nil => 0 - } - fn depth<T>( - t: Tree<T>) -> Int = - match t { - Leaf { _ } => 1 - Node { left, right } => - 1 + max( - depth(left), - depth(right)) - } + + // primitive consumer + fn unwrap_str( + s: Sum<Str, Str>) -> Str = + match s { + Left { value } => value + Right { value } => value } + // helpers + fn map<A,B,C>(s: Sum<A,B>, + f: A->C) -> Sum<C,B> = + match s { + Left { value } => Left { value: f(value) } + Right { value } => Right { value } } + fn and_then<A,B,C>(s: Sum<A,B>, + f: A->Sum<C,B>) -> Sum<C,B> = + match s { + Left { value } => f(value) + Right { value } => Right { value } } + // library + fn parse_int(s: Str) + -> Sum<ParseError, Int> + + fn http_get(url: Str) + -> Sum<HttpError, Bytes> + + fn validate_email(s: Str) + -> Sum<ValidationError, Email> + // app — composes everything + fn handle_signup( + req: SignupRequest + ) -> Sum<AppError, Session> = + and_then( + validate_email(req.email), + fn(email) => + and_then( + http_get(profile_url(email)), + fn(bytes) => + map( + parse_int(bytes_to_str(bytes)), + fn(uid) => mint_session(uid)))) - + RUST - + #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub struct Pair<A, B> { pub first: A, pub second: B } - impl<A, B> Pair<A, B> { - pub fn new(first: A, second: B) -> Self { Self { first, second } } - } - #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub enum Sum<A, B> { Left { value: A }, Right { value: B } } - impl<A, B> Sum<A, B> { - pub fn left(value: A) -> Self { Self::Left { value } } - pub fn right(value: B) -> Self { Self::Right { value } } - pub fn is_left(&self) -> bool { matches!(self, Self::Left {..}) } - pub fn is_right(&self) -> bool { matches!(self, Self::Right {..}) } - } - #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub enum Option<T> { Some { value: T }, None } - impl<T> Option<T> { - pub fn some(value: T) -> Self { Self::Some { value } } - pub fn none() -> Self { Self::None } - pub fn is_some(&self) -> bool { matches!(self, Self::Some {..}) } - pub fn is_none(&self) -> bool { matches!(self, Self::None) } - } - #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub enum Result<T, E> { Ok { value: T }, Err { error: E } } - impl<T, E> Result<T, E> { - pub fn ok(value: T) -> Self { Self::Ok { value } } - pub fn err(error: E) -> Self { Self::Err { error } } - pub fn is_ok(&self) -> bool { matches!(self, Self::Ok {..}) } - pub fn is_err(&self) -> bool { matches!(self, Self::Err {..}) } - } - #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub enum List<T> { - Cons { head: T, tail: Box<List<T>> }, - Nil, - } - impl<T> List<T> { - pub fn cons(head: T, tail: List<T>) -> Self { - Self::Cons { head, tail: Box::new(tail) } - } - pub fn nil() -> Self { Self::Nil } - pub fn is_cons(&self) -> bool { matches!(self, Self::Cons {..}) } - pub fn is_nil(&self) -> bool { matches!(self, Self::Nil) } - } - #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub enum Tree<T> { - Leaf { value: T }, - Node { left: Box<Tree<T>>, right: Box<Tree<T>> }, - } - impl<T> Tree<T> { - pub fn leaf(value: T) -> Self { Self::Leaf { value } } - pub fn node(left: Tree<T>, right: Tree<T>) -> Self { - Self::Node { left: Box::new(left), right: Box::new(right) } - } - pub fn is_leaf(&self) -> bool { matches!(self, Self::Leaf {..}) } - pub fn is_node(&self) -> bool { matches!(self, Self::Node {..}) } - } - pub fn swap<A, B>(p: Pair<A, B>) -> Pair<B, A> { - Pair { first: p.second, second: p.first } - } - pub fn unwrap_str(s: Sum<String, String>) -> String { - match s { - Sum::Left { value } => value, - Sum::Right { value } => value, - } - } - pub fn or_else<T>(o: Option<T>, d: T) -> T { - match o { - Option::Some { value } => value, - Option::None => d, - } - } - pub fn unwrap_or<T, E>(r: Result<T, E>, d: T) -> T { - match r { - Result::Ok { value } => value, - Result::Err { error: _ } => d, - } - } - pub fn length<T>(l: &List<T>) -> i64 { - match l { - List::Cons { tail, .. } => 1 + length(tail), - List::Nil => 0, - } - } - pub fn depth<T>(t: &Tree<T>) -> i64 { - match t { - Tree::Leaf { .. } => 1, - Tree::Node { left, right } => - 1 + std::cmp::max(depth(left), depth(right)), - } - } + pub enum Sum<A, B> { Left { value: A }, Right { value: B } } + impl<A, B> Sum<A, B> { + pub fn left(value: A) -> Self { Self::Left { value } } + pub fn right(value: B) -> Self { Self::Right { value } } + pub fn is_left(&self) -> bool { matches!(self, Self::Left {..}) } + pub fn is_right(&self) -> bool { matches!(self, Self::Right {..}) } + } + pub type Result<T, E> = Sum<E, T>; + pub fn unwrap_str(s: Sum<String, String>) -> String { + match s { + Sum::Left { value } => value, + Sum::Right { value } => value, + } + } + pub fn map<A, B, C>(s: Sum<A, B>, f: impl FnOnce(A) -> C) + -> Sum<C, B> { + match s { + Sum::Left { value } => Sum::Left { value: f(value) }, + Sum::Right { value } => Sum::Right { value }, + } + } + pub fn and_then<A, B, C>(s: Sum<A, B>, + f: impl FnOnce(A) -> Sum<C, B>) -> Sum<C, B> { + match s { + Sum::Left { value } => f(value), + Sum::Right { value } => Sum::Right { value }, + } + } + pub fn parse_int(s: &str) -> Sum<ParseError, i64> { + s.parse::<i64>().map_or_else( + |e| Sum::Left { value: ParseError::from(e) }, + |n| Sum::Right { value: n }) + } + pub fn http_get(url: &str) -> Sum<HttpError, Bytes> { + /* fetch implementation */ + } + pub fn validate_email(s: &str) -> Sum<ValidationError, Email> { + /* regex implementation */ + } + pub fn handle_signup(req: SignupRequest) + -> Sum<AppError, Session> { + and_then( + validate_email(&req.email), + |email| and_then( + http_get(&profile_url(&email)) + .map_left(AppError::Http), + |bytes| map( + parse_int(&bytes_to_str(bytes)) + .map_left(AppError::Parse), + |uid| mint_session(uid)))) + } - + .dag - - type Pair<A, B> { - first: A; second: B - } - type Sum<A, B> = - Left{value:A}|Right{value:B} - type Option<T> = - Some{value:T} | None - type Result<T, E> = - Ok{value:T} | Err{error:E} - type List<T> = - Cons { head: T, - tail: List<T> } - | Nil - type Tree<T> = - Leaf { value: T } - | Node { left: Tree<T>, - right: Tree<T> } + + type Sum<A, B> = + Left{value:A} | Right{value:B} + type Result<T, E> = Sum<E, T> + + SAME 5 LINES OF FACTS - Wings: ~50 lines. Middle: ~170 lines. Add another fact — wings grow by 3, middle grows by 15. The source stays trivial to maintain; the centerpiece is what runs. + 5 lines of facts. ~70 lines of business logic on the left. ~150 lines of generated Rust in the middle. The fact never changed. The middle is what runs. From a040fa9225100ff973e80c7ab018b975eced4c76 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 6 May 2026 20:14:05 +0000 Subject: [PATCH 06/16] docs: address codex review + relabel columns per feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Findings from codex review at SHA 1f310c42: 1. 02-add-languages.svg: "missing close" — was using { ... } as a placeholder body for and_then. Replaced with the real match body so the snippet now compiles. SVG height extended to fit. 2. 04-now-scale.svg: "Box recursive children not dereferenced" — obsoleted by the e5108c2 pivot to a single Sum primitive (List/Tree were removed entirely; no Box or recursion remains). Also fixed similar non-compiling placeholders the reviewer would have caught next pass: - 01: { ... } in unwrap_str → real match body - 03: { ... } in http_get / validate_email → unimplemented!() - 04: /* fetch implementation */ / /* regex implementation */ → unimplemented!() (a comment-only body doesn't match the Sum return) - 04: handle_signup used .map_left() which Sum's impl block doesn't expose. Simplified to a single and_then chain returning Sum so the snippet uses only methods actually in the emitted impl block. Column relabel per separate feedback: - LEFT: "BUSINESS LOGIC" → "BUSINESS LOGIC · HELPERS · LIBRARIES" - RIGHT: "FACTS" → "FACTS · STRUCTS · OBJECTS" --- .../emission-intuition/01-start-small.svg | 17 +++++--- .../emission-intuition/02-add-languages.svg | 17 +++++--- .../demos/emission-intuition/03-add-types.svg | 8 ++-- .../demos/emission-intuition/04-now-scale.svg | 41 +++++++------------ 4 files changed, 41 insertions(+), 42 deletions(-) diff --git a/docs/demos/emission-intuition/01-start-small.svg b/docs/demos/emission-intuition/01-start-small.svg index 72346268a5d..3740e86eea9 100644 --- a/docs/demos/emission-intuition/01-start-small.svg +++ b/docs/demos/emission-intuition/01-start-small.svg @@ -1,4 +1,4 @@ - + diff --git a/docs/demos/emission-intuition/02-add-languages.svg b/docs/demos/emission-intuition/02-add-languages.svg index a02e4875337..bb78295143a 100644 --- a/docs/demos/emission-intuition/02-add-languages.svg +++ b/docs/demos/emission-intuition/02-add-languages.svg @@ -1,4 +1,4 @@ - + diff --git a/docs/demos/emission-intuition/03-add-types.svg b/docs/demos/emission-intuition/03-add-types.svg index a3e08e7fa11..5fbb30629fd 100644 --- a/docs/demos/emission-intuition/03-add-types.svg +++ b/docs/demos/emission-intuition/03-add-types.svg @@ -19,9 +19,9 @@ A library built from those helpers. Same Sum on the right (with one alias). Library functions on the left use Sum at concrete types like Int and HttpError. - BUSINESS LOGIC + BUSINESS LOGIC · HELPERS · LIBRARIES RUST — GENERATED - FACTS + FACTS · STRUCTS · OBJECTS @@ -98,8 +98,8 @@ |e| Sum::Left { value: ParseError::from(e) }, |n| Sum::Right { value: n }) } - pub fn http_get(url: &str) -> Sum<HttpError, Bytes> { ... } - pub fn validate_email(s: &str) -> Sum<ValidationError, Email> { ... } + pub fn http_get(url: &str) -> Sum<HttpError, Bytes> { unimplemented!() } + pub fn validate_email(s: &str) -> Sum<ValidationError, Email> { unimplemented!() } diff --git a/docs/demos/emission-intuition/04-now-scale.svg b/docs/demos/emission-intuition/04-now-scale.svg index b56378be0cd..15697ca5646 100644 --- a/docs/demos/emission-intuition/04-now-scale.svg +++ b/docs/demos/emission-intuition/04-now-scale.svg @@ -20,9 +20,9 @@ An app uses the library. Same Sum on the right. App handler on the left composes library functions through the helpers. The middle is the real Rust module. - BUSINESS LOGIC + BUSINESS LOGIC · HELPERS · LIBRARIES RUST — GENERATED - FACTS + FACTS · STRUCTS · OBJECTS @@ -54,19 +54,14 @@ fn validate_email(s: Str) -> Sum<ValidationError, Email> - // app — composes everything + // app — composes the library fn handle_signup( req: SignupRequest - ) -> Sum<AppError, Session> = + ) -> Sum<HttpError, Session> = and_then( - validate_email(req.email), - fn(email) => - and_then( - http_get(profile_url(email)), - fn(bytes) => - map( - parse_int(bytes_to_str(bytes)), - fn(uid) => mint_session(uid)))) + http_get(profile_url(req.email)), + fn(bytes) => + Right { value: mint_session(bytes) }) @@ -107,23 +102,17 @@ |n| Sum::Right { value: n }) } pub fn http_get(url: &str) -> Sum<HttpError, Bytes> { - /* fetch implementation */ + unimplemented!() } pub fn validate_email(s: &str) -> Sum<ValidationError, Email> { - /* regex implementation */ + unimplemented!() } - pub fn handle_signup(req: SignupRequest) - -> Sum<AppError, Session> { - and_then( - validate_email(&req.email), - |email| and_then( - http_get(&profile_url(&email)) - .map_left(AppError::Http), - |bytes| map( - parse_int(&bytes_to_str(bytes)) - .map_left(AppError::Parse), - |uid| mint_session(uid)))) - } + pub fn handle_signup(req: SignupRequest) -> Sum<HttpError, Session> { + and_then( + http_get(&profile_url(&req.email)), + |bytes| Sum::Right { value: mint_session(bytes) }, + ) + } From 51438c55f6e81084b86c0e8b428304efa87eb1c8 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 6 May 2026 20:16:06 +0000 Subject: [PATCH 07/16] docs: correct line counts in punchlines + tighten American English MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 01: "3 lines of facts" → "2 lines" (the Sum decl renders as two visible lines, not three). - 03: "Five lines of facts" → "Three lines" (Sum 2-line + Result alias 1-line = 3 visible lines). - 04: "5 lines of facts" / "SAME 5 LINES OF FACTS" → "3 lines" / "SAME 3 LINES" to match the actual rendered FACTS column. - Misc voice tightening: "as we layer more in" → "as we add more layers"; "use Sum at concrete types" → "use Sum with concrete types"; "The fact didn't move" → "The facts didn't change". - Punchline LOC estimates on 04 brought into line with the simplified handle_signup (~60 logic / ~140 Rust, down from the pre-simplification estimates). --- docs/demos/emission-intuition/01-start-small.svg | 2 +- docs/demos/emission-intuition/02-add-languages.svg | 2 +- docs/demos/emission-intuition/03-add-types.svg | 4 ++-- docs/demos/emission-intuition/04-now-scale.svg | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/demos/emission-intuition/01-start-small.svg b/docs/demos/emission-intuition/01-start-small.svg index 3740e86eea9..8580b7379b0 100644 --- a/docs/demos/emission-intuition/01-start-small.svg +++ b/docs/demos/emission-intuition/01-start-small.svg @@ -63,5 +63,5 @@ type Sum<A, B> = Left{value:A} | Right{value:B} - 3 lines of facts. 7 lines of logic. ~18 lines of Rust. The fact will not grow as we add layers. + 2 lines of facts. 7 lines of logic. ~18 lines of Rust. These facts won't grow as we add more layers. diff --git a/docs/demos/emission-intuition/02-add-languages.svg b/docs/demos/emission-intuition/02-add-languages.svg index bb78295143a..ed7bbbdf383 100644 --- a/docs/demos/emission-intuition/02-add-languages.svg +++ b/docs/demos/emission-intuition/02-add-languages.svg @@ -94,5 +94,5 @@ FACTS UNCHANGED - The fact didn't move. The logic added a couple of helpers. The middle expanded by Sum's full impl block plus three Rust function bodies. + The facts didn't change. We added two helpers, and the middle grew to hold Sum's full impl block plus three Rust function bodies. diff --git a/docs/demos/emission-intuition/03-add-types.svg b/docs/demos/emission-intuition/03-add-types.svg index 5fbb30629fd..97936715f1d 100644 --- a/docs/demos/emission-intuition/03-add-types.svg +++ b/docs/demos/emission-intuition/03-add-types.svg @@ -17,7 +17,7 @@ A library built from those helpers. - Same Sum on the right (with one alias). Library functions on the left use Sum at concrete types like Int and HttpError. + Same Sum on the right (with one alias). Library functions on the left use Sum with concrete types like Int and HttpError. BUSINESS LOGIC · HELPERS · LIBRARIES RUST — GENERATED @@ -111,5 +111,5 @@ FACTS GREW BY ONE LINE - Five lines of facts. The library on the left compounded — and the middle compounded faster. + Three lines of facts. The library on the left got longer — and the middle got longer faster. diff --git a/docs/demos/emission-intuition/04-now-scale.svg b/docs/demos/emission-intuition/04-now-scale.svg index 15697ca5646..6d27ed77e48 100644 --- a/docs/demos/emission-intuition/04-now-scale.svg +++ b/docs/demos/emission-intuition/04-now-scale.svg @@ -122,7 +122,7 @@ Left{value:A} | Right{value:B} type Result<T, E> = Sum<E, T> - SAME 5 LINES OF FACTS + SAME 3 LINES OF FACTS - 5 lines of facts. ~70 lines of business logic on the left. ~150 lines of generated Rust in the middle. The fact never changed. The middle is what runs. + 3 lines of facts. ~60 lines of business logic on the left. ~140 lines of generated Rust in the middle. The facts didn't change. The middle is what runs. From 77010a9dec89a953b82ac30f578d915401bad820 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 6 May 2026 20:26:06 +0000 Subject: [PATCH 08/16] =?UTF-8?q?docs:=20layered=20facts=20=E2=80=94=20pri?= =?UTF-8?q?mitive=20=E2=86=92=20helpers=20=E2=86=92=20library=20=E2=86=92?= =?UTF-8?q?=20domain?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per review feedback: the FACTS column now shows progressively deeper layers, the BUSINESS LOGIC column interacts only with the top one or two, and the lower layers are abstracted away (never named in source) while still being fully materialized in the generated middle. 01: 1 layer — PRIMITIVE only. 02: 2 layers — + HELPERS (alias). Helpers still know about the primitive (they're combinators on Sum) and operate at that layer. 03: 3 layers — + LIBRARY (ApiResponse, HttpError). Library functions on the left return ApiResponse and never name Sum or Result. 04: 4 layers — + DOMAIN (LoginRequest, UserSession, User, Credentials). The handler only writes against domain + library types; Sum, Result, helpers — all materialized in the middle, never named on the left. Each FACTS layer is separated by a // LAYER label in italic gray; the business-side layer comments mirror them so the reader can match what's used vs what's emitted. --- .../emission-intuition/01-start-small.svg | 19 +- .../emission-intuition/02-add-languages.svg | 68 +++--- .../demos/emission-intuition/03-add-types.svg | 166 +++++++------- .../demos/emission-intuition/04-now-scale.svg | 215 ++++++++++-------- 4 files changed, 243 insertions(+), 225 deletions(-) diff --git a/docs/demos/emission-intuition/01-start-small.svg b/docs/demos/emission-intuition/01-start-small.svg index 8580b7379b0..c02591c0078 100644 --- a/docs/demos/emission-intuition/01-start-small.svg +++ b/docs/demos/emission-intuition/01-start-small.svg @@ -14,14 +14,14 @@ - One primitive. - A single Sum type. One function that uses it. The starting point. + One layer. + A primitive on the right. A small consumer of it on the left. The compiler emits a working module. BUSINESS LOGIC · HELPERS · LIBRARIES RUST — GENERATED FACTS · STRUCTS · OBJECTS - + .dag @@ -34,7 +34,7 @@ Right { value } => value } - + RUST @@ -56,12 +56,13 @@ } } - + .dag - - type Sum<A, B> = - Left{value:A} | Right{value:B} + + // PRIMITIVE + type Sum<A, B> = + Left{value:A} | Right{value:B} - 2 lines of facts. 7 lines of logic. ~18 lines of Rust. These facts won't grow as we add more layers. + One layer of facts. One consumer. The middle is what runs. diff --git a/docs/demos/emission-intuition/02-add-languages.svg b/docs/demos/emission-intuition/02-add-languages.svg index ed7bbbdf383..673d635a117 100644 --- a/docs/demos/emission-intuition/02-add-languages.svg +++ b/docs/demos/emission-intuition/02-add-languages.svg @@ -11,19 +11,17 @@ .layer { font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; font-size: 11px; font-style: italic; fill: #6e7781; } .card { fill: #ffffff; stroke: #d0d7de; stroke-width: 1; } .card-header { fill: #f6f8fa; stroke: #d0d7de; stroke-width: 1; } - .stable { fill: #ddf4ff; stroke: #54aeff; stroke-width: 1; } - .stable-text { font-size: 10px; font-weight: 600; fill: #0969da; letter-spacing: 0.04em; } - Helpers built on it. - Same Sum on the right. Two generic combinators on the left. The middle holds Sum's emission and three function bodies. + Two layers — primitive and helpers. + A second layer on the right (an alias). Helper combinators on the left, still operating at the primitive layer. BUSINESS LOGIC · HELPERS · LIBRARIES RUST — GENERATED FACTS · STRUCTS · OBJECTS - + .dag @@ -34,7 +32,7 @@ Left { value } => value Right { value } => value } - // helpers (generic over Sum) + // generic helpers (still on Sum) fn map<A, B, C>( s: Sum<A, B>, f: A -> C @@ -52,7 +50,7 @@ Left { value } => f(value) Right { value } => Right { value } } - + RUST @@ -64,35 +62,37 @@ pub fn is_left(&self) -> bool { matches!(self, Self::Left {..}) } pub fn is_right(&self) -> bool { matches!(self, Self::Right {..}) } } - pub fn unwrap_str(s: Sum<String, String>) -> String { - match s { - Sum::Left { value } => value, - Sum::Right { value } => value, - } - } - pub fn map<A, B, C>(s: Sum<A, B>, f: impl FnOnce(A) -> C) - -> Sum<C, B> { - match s { - Sum::Left { value } => Sum::Left { value: f(value) }, - Sum::Right { value } => Sum::Right { value }, - } - } - pub fn and_then<A, B, C>(s: Sum<A, B>, - f: impl FnOnce(A) -> Sum<C, B>) -> Sum<C, B> { - match s { - Sum::Left { value } => f(value), - Sum::Right { value } => Sum::Right { value }, - } - } + pub type Result<T, E> = Sum<E, T>; + pub fn unwrap_str(s: Sum<String, String>) -> String { + match s { + Sum::Left { value } => value, + Sum::Right { value } => value, + } + } + pub fn map<A, B, C>(s: Sum<A, B>, f: impl FnOnce(A) -> C) + -> Sum<C, B> { + match s { + Sum::Left { value } => Sum::Left { value: f(value) }, + Sum::Right { value } => Sum::Right { value }, + } + } + pub fn and_then<A, B, C>(s: Sum<A, B>, + f: impl FnOnce(A) -> Sum<C, B>) -> Sum<C, B> { + match s { + Sum::Left { value } => f(value), + Sum::Right { value } => Sum::Right { value }, + } - + .dag - - type Sum<A, B> = - Left{value:A} | Right{value:B} - - FACTS UNCHANGED + + // PRIMITIVE + type Sum<A, B> = + Left{value:A} | Right{value:B} + // HELPERS (alias) + type Result<T, E> = + Sum<E, T> - The facts didn't change. We added two helpers, and the middle grew to hold Sum's full impl block plus three Rust function bodies. + Two layers of facts. The helpers on the left know about the primitive — and they're enough to power everything that comes next. diff --git a/docs/demos/emission-intuition/03-add-types.svg b/docs/demos/emission-intuition/03-add-types.svg index 97936715f1d..cf2ace171dd 100644 --- a/docs/demos/emission-intuition/03-add-types.svg +++ b/docs/demos/emission-intuition/03-add-types.svg @@ -1,4 +1,4 @@ - + - A library built from those helpers. - Same Sum on the right (with one alias). Library functions on the left use Sum with concrete types like Int and HttpError. + Three layers — add a library on top. + Library types in the right column; library functions on the left use them. The primitive and helpers are abstracted — the left column never names Sum. BUSINESS LOGIC · HELPERS · LIBRARIES RUST — GENERATED FACTS · STRUCTS · OBJECTS - + .dag - - // primitive consumer - fn unwrap_str( - s: Sum<Str, Str>) -> Str = - match s { - Left { value } => value - Right { value } => value } - // helpers - fn map<A,B,C>( - s: Sum<A,B>, f: A->C) -> Sum<C,B> = - match s { - Left { value } => Left { value: f(value) } - Right { value } => Right { value } } - fn and_then<A,B,C>( - s: Sum<A,B>, f: A->Sum<C,B>) -> Sum<C,B> = - match s { - Left { value } => f(value) - Right { value } => Right { value } } - // library — Sum with concrete primitives - fn parse_int( - s: Str - ) -> Sum<ParseError, Int> = - // ... try parse, return Left on err - - fn http_get( - url: Str - ) -> Sum<HttpError, Bytes> = - // ... fetch, return Left on err - fn validate_email( - s: Str - ) -> Sum<ValidationError, Email> = - // ... regex check etc. + + // library — abstracts Sum/Result + fn http_get( + url: Str + ) -> ApiResponse<Bytes> + fn parse_user( + bytes: Bytes + ) -> ApiResponse<User> + // composes the library + fn fetch_user( + id: UserId + ) -> ApiResponse<User> = + and_then( + http_get(format("/u/{id}")), + fn(bytes) => parse_user(bytes)) + // note: fetch_user does not + // pattern-match Sum or Result. + // Lower layers are abstracted. - + RUST - - #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub enum Sum<A, B> { Left { value: A }, Right { value: B } } - impl<A, B> Sum<A, B> { - pub fn left(value: A) -> Self { Self::Left { value } } - pub fn right(value: B) -> Self { Self::Right { value } } - pub fn is_left(&self) -> bool { matches!(self, Self::Left {..}) } - pub fn is_right(&self) -> bool { matches!(self, Self::Right {..}) } - } - // alias from .dag - pub type Result<T, E> = Sum<E, T>; - pub fn unwrap_str(s: Sum<String, String>) -> String { - match s { - Sum::Left { value } => value, - Sum::Right { value } => value, - } - } - pub fn map<A, B, C>(s: Sum<A, B>, f: impl FnOnce(A) -> C) - -> Sum<C, B> { - match s { - Sum::Left { value } => Sum::Left { value: f(value) }, - Sum::Right { value } => Sum::Right { value }, - } - } - pub fn and_then<A, B, C>(s: Sum<A, B>, - f: impl FnOnce(A) -> Sum<C, B>) -> Sum<C, B> { - match s { - Sum::Left { value } => f(value), - Sum::Right { value } => Sum::Right { value }, - } - } - pub fn parse_int(s: &str) -> Sum<ParseError, i64> { - s.parse::<i64>().map_or_else( - |e| Sum::Left { value: ParseError::from(e) }, - |n| Sum::Right { value: n }) - } - pub fn http_get(url: &str) -> Sum<HttpError, Bytes> { unimplemented!() } - pub fn validate_email(s: &str) -> Sum<ValidationError, Email> { unimplemented!() } + + // primitive — full impl + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub enum Sum<A, B> { Left { value: A }, Right { value: B } } + impl<A, B> Sum<A, B> { + pub fn left(value: A) -> Self { Self::Left { value } } + pub fn right(value: B) -> Self { Self::Right { value } } + pub fn is_left(&self) -> bool { matches!(self, Self::Left {..}) } + pub fn is_right(&self) -> bool { matches!(self, Self::Right {..}) } + } + // helpers — derived aliases + combinators + pub type Result<T, E> = Sum<E, T>; + pub fn map<A, B, C>(s: Sum<A, B>, f: impl FnOnce(A) -> C) + -> Sum<C, B> { + match s { + Sum::Left { value } => Sum::Left { value: f(value) }, + Sum::Right { value } => Sum::Right { value }, + } + } + pub fn and_then<A, B, C>(s: Sum<A, B>, + f: impl FnOnce(A) -> Sum<C, B>) -> Sum<C, B> { + match s { + Sum::Left { value } => f(value), + Sum::Right { value } => Sum::Right { value }, + } + } + // library — domain alias + structs + pub type ApiResponse<T> = Result<T, HttpError>; + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub struct HttpError { pub status: i32, pub body: String } + impl HttpError { + pub fn new(status: i32, body: String) -> Self { Self { status, body } } + } + pub fn http_get(url: &str) -> ApiResponse<Bytes> { unimplemented!() } + pub fn parse_user(bytes: Bytes) -> ApiResponse<User> { unimplemented!() } + pub fn fetch_user(id: UserId) -> ApiResponse<User> { + and_then( + http_get(&format!("/u/{}", id)), + |bytes| parse_user(bytes), + ) + } - + .dag - - type Sum<A, B> = - Left{value:A} | Right{value:B} - type Result<T, E> = Sum<E, T> - - FACTS GREW BY ONE LINE + + // PRIMITIVE + type Sum<A, B> = + Left{value:A} | Right{value:B} + // HELPERS + type Result<T, E> = + Sum<E, T> + // LIBRARY + type ApiResponse<T> = + Result<T, HttpError> + type HttpError { + status: Int + body: Str + } - Three lines of facts. The library on the left got longer — and the middle got longer faster. + Three stacked layers on the right. Library code on the left only mentions the top one. Everything underneath still gets emitted. diff --git a/docs/demos/emission-intuition/04-now-scale.svg b/docs/demos/emission-intuition/04-now-scale.svg index 6d27ed77e48..a0837238d4b 100644 --- a/docs/demos/emission-intuition/04-now-scale.svg +++ b/docs/demos/emission-intuition/04-now-scale.svg @@ -1,4 +1,4 @@ - + - An app uses the library. - Same Sum on the right. App handler on the left composes library functions through the helpers. The middle is the real Rust module. + Four layers — primitive · helpers · library · domain. + A business handler on the left. It talks only to the domain and library. Sum, Result, the helpers — all abstracted away in the source, all materialized in the middle. BUSINESS LOGIC · HELPERS · LIBRARIES RUST — GENERATED FACTS · STRUCTS · OBJECTS - + .dag - - // primitive consumer - fn unwrap_str( - s: Sum<Str, Str>) -> Str = - match s { - Left { value } => value - Right { value } => value } - // helpers - fn map<A,B,C>(s: Sum<A,B>, - f: A->C) -> Sum<C,B> = - match s { - Left { value } => Left { value: f(value) } - Right { value } => Right { value } } - fn and_then<A,B,C>(s: Sum<A,B>, - f: A->Sum<C,B>) -> Sum<C,B> = - match s { - Left { value } => f(value) - Right { value } => Right { value } } - // library - fn parse_int(s: Str) - -> Sum<ParseError, Int> - - fn http_get(url: Str) - -> Sum<HttpError, Bytes> - - fn validate_email(s: Str) - -> Sum<ValidationError, Email> - // app — composes the library - fn handle_signup( - req: SignupRequest - ) -> Sum<HttpError, Session> = - and_then( - http_get(profile_url(req.email)), - fn(bytes) => - Right { value: mint_session(bytes) }) + + // library — known signatures + fn validate( + req: LoginRequest + ) -> ApiResponse<Credentials> + fn fetch_user( + creds: Credentials + ) -> ApiResponse<User> + fn mint_session( + user: User + ) -> ApiResponse<UserSession> + // app — handler composes them + fn handle_login( + req: LoginRequest + ) -> ApiResponse<UserSession> = + and_then( + validate(req), + fn(creds) => + and_then( + fetch_user(creds), + fn(user) => mint_session(user))) + // the handler doesn't say "Sum" + // or "Result" anywhere. Lower + // layers are abstract here. - + RUST - - #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub enum Sum<A, B> { Left { value: A }, Right { value: B } } - impl<A, B> Sum<A, B> { - pub fn left(value: A) -> Self { Self::Left { value } } - pub fn right(value: B) -> Self { Self::Right { value } } - pub fn is_left(&self) -> bool { matches!(self, Self::Left {..}) } - pub fn is_right(&self) -> bool { matches!(self, Self::Right {..}) } - } - pub type Result<T, E> = Sum<E, T>; - pub fn unwrap_str(s: Sum<String, String>) -> String { - match s { - Sum::Left { value } => value, - Sum::Right { value } => value, - } - } - pub fn map<A, B, C>(s: Sum<A, B>, f: impl FnOnce(A) -> C) - -> Sum<C, B> { - match s { - Sum::Left { value } => Sum::Left { value: f(value) }, - Sum::Right { value } => Sum::Right { value }, - } - } - pub fn and_then<A, B, C>(s: Sum<A, B>, - f: impl FnOnce(A) -> Sum<C, B>) -> Sum<C, B> { - match s { - Sum::Left { value } => f(value), - Sum::Right { value } => Sum::Right { value }, - } - } - pub fn parse_int(s: &str) -> Sum<ParseError, i64> { - s.parse::<i64>().map_or_else( - |e| Sum::Left { value: ParseError::from(e) }, - |n| Sum::Right { value: n }) - } - pub fn http_get(url: &str) -> Sum<HttpError, Bytes> { - unimplemented!() - } - pub fn validate_email(s: &str) -> Sum<ValidationError, Email> { - unimplemented!() - } - pub fn handle_signup(req: SignupRequest) -> Sum<HttpError, Session> { - and_then( - http_get(&profile_url(&req.email)), - |bytes| Sum::Right { value: mint_session(bytes) }, - ) - } + + // primitive + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub enum Sum<A, B> { Left { value: A }, Right { value: B } } + impl<A, B> Sum<A, B> { + pub fn left(value: A) -> Self { Self::Left { value } } + pub fn right(value: B) -> Self { Self::Right { value } } + pub fn is_left(&self) -> bool { matches!(self, Self::Left {..}) } + pub fn is_right(&self) -> bool { matches!(self, Self::Right {..}) } + } + // helpers + pub type Result<T, E> = Sum<E, T>; + pub fn map<A, B, C>(s: Sum<A, B>, f: impl FnOnce(A) -> C) + -> Sum<C, B> { + match s { + Sum::Left { value } => Sum::Left { value: f(value) }, + Sum::Right { value } => Sum::Right { value }, + } + } + pub fn and_then<A, B, C>(s: Sum<A, B>, + f: impl FnOnce(A) -> Sum<C, B>) -> Sum<C, B> { + match s { Sum::Left{value}=>f(value), Sum::Right{value}=>Sum::Right{value} } + } + // library + pub type ApiResponse<T> = Result<T, HttpError>; + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub struct HttpError { pub status: i32, pub body: String } + impl HttpError { + pub fn new(status: i32, body: String) -> Self { Self { status, body } } + } + // domain + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub struct LoginRequest { pub email: String, pub password: String } + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub struct Credentials { pub email: String, pub hash: Vec<u8> } + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub struct User { pub id: UserId, pub email: String } + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub struct UserSession { pub user_id: UserId, pub expires_at: Timestamp } + impl LoginRequest { + pub fn new(email: String, password: String) -> Self { + Self { email, password } + } + } + pub fn validate(req: LoginRequest) -> ApiResponse<Credentials> { unimplemented!() } + pub fn fetch_user(creds: Credentials) -> ApiResponse<User> { unimplemented!() } + pub fn mint_session(user: User) -> ApiResponse<UserSession> { unimplemented!() } + // app handler — composes the library + pub fn handle_login( + req: LoginRequest, + ) -> ApiResponse<UserSession> { + and_then( + validate(req), + |creds| and_then( + fetch_user(creds), + |user| mint_session(user), + ), + ) + } - + .dag - - type Sum<A, B> = - Left{value:A} | Right{value:B} - type Result<T, E> = Sum<E, T> - - SAME 3 LINES OF FACTS + + // PRIMITIVE + type Sum<A, B> = + Left{value:A} | Right{value:B} + // HELPERS + type Result<T, E> = + Sum<E, T> + // LIBRARY + type ApiResponse<T> = + Result<T, HttpError> + type HttpError { + status: Int; body: Str + } + // DOMAIN + type LoginRequest { + email: Str + password: Str + } + type Credentials { + email: Str; hash: Bytes + } + type User { + id: UserId; email: Str + } + type UserSession { + user_id: UserId + expires_at: Timestamp + } - 3 lines of facts. ~60 lines of business logic on the left. ~140 lines of generated Rust in the middle. The facts didn't change. The middle is what runs. + Four stacked layers on the right. The handler on the left only writes against the top two. The middle has the whole stack built and ready to run. From 66ed08afce966d85ed0904ccd87d59946f02e149 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 6 May 2026 20:29:07 +0000 Subject: [PATCH 09/16] =?UTF-8?q?docs:=20add=2004-colored.svg=20=E2=80=94?= =?UTF-8?q?=20color-coded=20correlation=20variant?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pilot of an alternate / "colored" version of 04-now-scale.svg. Same content; each concept gets a tinted background and the same color appears wherever that concept lives so the reader can trace a fact on the right (or a slice of business logic on the left) to its expansion in the middle. Color legend: blue — Primitive (Sum) green — Helpers (Result alias, map, and_then) purple — Library types (ApiResponse, HttpError) amber — Library functions (validate / fetch_user / mint_session) pink — Domain types (LoginRequest, Credentials, User, UserSession) yellow — App handler (handle_login) Original 04-now-scale.svg untouched. Operators can pick whichever variant lands better. If the colored treatment helps, will fan out to 03-colored.svg etc. --- docs/demos/emission-intuition/04-colored.svg | 206 +++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 docs/demos/emission-intuition/04-colored.svg diff --git a/docs/demos/emission-intuition/04-colored.svg b/docs/demos/emission-intuition/04-colored.svg new file mode 100644 index 00000000000..108f6fdead0 --- /dev/null +++ b/docs/demos/emission-intuition/04-colored.svg @@ -0,0 +1,206 @@ + + + + + + Four layers — color-coded. + Same diagram as 04. Each concept gets a tint; the same color shows up wherever that concept appears, so you can trace a fact on the right (or a bit of logic on the left) to its expansion in the middle. + + + + + Primitive (Sum) + + Helpers (Result, map, and_then) + + Library types (ApiResponse, HttpError) + + Library fns (validate / fetch_user / mint_session) + + Domain types (LoginRequest, User, UserSession, …) + + App handler (handle_login) + + + BUSINESS LOGIC · HELPERS · LIBRARIES + RUST — GENERATED + FACTS · STRUCTS · OBJECTS + + + + .dag + + + + + + + // library — known signatures + fn validate( + req: LoginRequest + ) -> ApiResponse<Credentials> + fn fetch_user( + creds: Credentials + ) -> ApiResponse<User> + fn mint_session( + user: User + ) -> ApiResponse<UserSession> + // app — handler composes them + fn handle_login( + req: LoginRequest + ) -> ApiResponse<UserSession> = + and_then( + validate(req), + fn(creds) => + and_then( + fetch_user(creds), + fn(user) => mint_session(user))) + // the handler doesn't say "Sum" + // or "Result" anywhere. Lower + // layers are abstract here. + + + + RUST + + + + + + + + + + + // primitive + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub enum Sum<A, B> { Left { value: A }, Right { value: B } } + impl<A, B> Sum<A, B> { + pub fn left(value: A) -> Self { Self::Left { value } } + pub fn right(value: B) -> Self { Self::Right { value } } + pub fn is_left(&self) -> bool { matches!(self, Self::Left {..}) } + pub fn is_right(&self) -> bool { matches!(self, Self::Right {..}) } + } + + // helpers + pub type Result<T, E> = Sum<E, T>; + pub fn map<A, B, C>(s: Sum<A, B>, f: impl FnOnce(A) -> C) + -> Sum<C, B> { + match s { + Sum::Left { value } => Sum::Left { value: f(value) }, + Sum::Right { value } => Sum::Right { value }, + } + } + pub fn and_then<A, B, C>(s: Sum<A, B>, + f: impl FnOnce(A) -> Sum<C, B>) -> Sum<C, B> { + match s { Sum::Left{value}=>f(value), Sum::Right{value}=>Sum::Right{value} } + } + + // library + pub type ApiResponse<T> = Result<T, HttpError>; + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub struct HttpError { pub status: i32, pub body: String } + impl HttpError { + pub fn new(status: i32, body: String) -> Self { Self { status, body } } + } + + // domain + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub struct LoginRequest { pub email: String, pub password: String } + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub struct Credentials { pub email: String, pub hash: Vec<u8> } + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub struct User { pub id: UserId, pub email: String } + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub struct UserSession { pub user_id: UserId, pub expires_at: Timestamp } + impl LoginRequest { + pub fn new(email: String, password: String) -> Self { + Self { email, password } + } + } + + pub fn validate(req: LoginRequest) -> ApiResponse<Credentials> { unimplemented!() } + pub fn fetch_user(creds: Credentials) -> ApiResponse<User> { unimplemented!() } + pub fn mint_session(user: User) -> ApiResponse<UserSession> { unimplemented!() } + + // app handler — composes the library + pub fn handle_login( + req: LoginRequest, + ) -> ApiResponse<UserSession> { + and_then( + validate(req), + |creds| and_then( + fetch_user(creds), + |user| mint_session(user), + ), + ) + } + + + + .dag + + + + + + + + + // PRIMITIVE + type Sum<A, B> = + Left{value:A} | Right{value:B} + + // HELPERS + type Result<T, E> = + Sum<E, T> + + // LIBRARY + type ApiResponse<T> = + Result<T, HttpError> + type HttpError { + status: Int; body: Str + } + + // DOMAIN + type LoginRequest { + email: Str + password: Str + } + type Credentials { + email: Str; hash: Bytes + } + type User { + id: UserId; email: Str + } + type UserSession { + user_id: UserId + expires_at: Timestamp + } + + Same color = same concept across columns. Trace blue, green, purple, pink, amber, yellow — each one shows up exactly where it lives, on either wing or in the middle. + From ec07f5487deae6a1fa2adf3718b19022b575f289 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 6 May 2026 20:37:46 +0000 Subject: [PATCH 10/16] =?UTF-8?q?docs:=2004-colored=20=E2=80=94=20colors?= =?UTF-8?q?=20per=20LAYER=20+=20generated=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per review: the colored variant should reflect the layered structure, not flat per-concept tinting. Now each LAYER (primitive, helpers, library, domain, app, tests) gets a single color, and that color appears wherever that layer has content — the LEFT column shows the logic for layers that have logic at them (helpers defs, library sigs, app handler), the RIGHT shows facts at every layer that has them, and the CENTER materializes everything. Adds a new TESTS layer (teal) — a #[cfg(test)] mod tests block emitted as part of the structural fold. Source on neither wing mentions it; the compiler emits it from the same fold that produces the impl blocks. Layered legend at the top spells out where each color appears: primitive — facts → generated (no logic) helpers — facts + logic → generated library — facts + logic → generated domain — facts → generated (no logic) app — logic → generated (no facts) tests — no source — emitted from the fold --- docs/demos/emission-intuition/04-colored.svg | 437 +++++++++++-------- 1 file changed, 251 insertions(+), 186 deletions(-) diff --git a/docs/demos/emission-intuition/04-colored.svg b/docs/demos/emission-intuition/04-colored.svg index 108f6fdead0..22d622bbba0 100644 --- a/docs/demos/emission-intuition/04-colored.svg +++ b/docs/demos/emission-intuition/04-colored.svg @@ -1,4 +1,4 @@ - + - Four layers — color-coded. - Same diagram as 04. Each concept gets a tint; the same color shows up wherever that concept appears, so you can trace a fact on the right (or a bit of logic on the left) to its expansion in the middle. - - - - - Primitive (Sum) - - Helpers (Result, map, and_then) - - Library types (ApiResponse, HttpError) - - Library fns (validate / fetch_user / mint_session) - - Domain types (LoginRequest, User, UserSession, …) - - App handler (handle_login) - - - BUSINESS LOGIC · HELPERS · LIBRARIES - RUST — GENERATED - FACTS · STRUCTS · OBJECTS - - - - .dag - - - - - - - // library — known signatures - fn validate( - req: LoginRequest - ) -> ApiResponse<Credentials> - fn fetch_user( - creds: Credentials - ) -> ApiResponse<User> - fn mint_session( - user: User - ) -> ApiResponse<UserSession> - // app — handler composes them - fn handle_login( - req: LoginRequest - ) -> ApiResponse<UserSession> = - and_then( - validate(req), - fn(creds) => - and_then( - fetch_user(creds), - fn(user) => mint_session(user))) - // the handler doesn't say "Sum" - // or "Result" anywhere. Lower - // layers are abstract here. - - - - RUST - - - - - - - - - - - // primitive - #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub enum Sum<A, B> { Left { value: A }, Right { value: B } } - impl<A, B> Sum<A, B> { - pub fn left(value: A) -> Self { Self::Left { value } } - pub fn right(value: B) -> Self { Self::Right { value } } - pub fn is_left(&self) -> bool { matches!(self, Self::Left {..}) } - pub fn is_right(&self) -> bool { matches!(self, Self::Right {..}) } - } - - // helpers - pub type Result<T, E> = Sum<E, T>; - pub fn map<A, B, C>(s: Sum<A, B>, f: impl FnOnce(A) -> C) - -> Sum<C, B> { - match s { - Sum::Left { value } => Sum::Left { value: f(value) }, - Sum::Right { value } => Sum::Right { value }, - } - } - pub fn and_then<A, B, C>(s: Sum<A, B>, - f: impl FnOnce(A) -> Sum<C, B>) -> Sum<C, B> { - match s { Sum::Left{value}=>f(value), Sum::Right{value}=>Sum::Right{value} } - } - - // library - pub type ApiResponse<T> = Result<T, HttpError>; - #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub struct HttpError { pub status: i32, pub body: String } - impl HttpError { - pub fn new(status: i32, body: String) -> Self { Self { status, body } } - } - - // domain - #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub struct LoginRequest { pub email: String, pub password: String } - #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub struct Credentials { pub email: String, pub hash: Vec<u8> } - #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub struct User { pub id: UserId, pub email: String } - #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub struct UserSession { pub user_id: UserId, pub expires_at: Timestamp } - impl LoginRequest { - pub fn new(email: String, password: String) -> Self { - Self { email, password } - } - } - - pub fn validate(req: LoginRequest) -> ApiResponse<Credentials> { unimplemented!() } - pub fn fetch_user(creds: Credentials) -> ApiResponse<User> { unimplemented!() } - pub fn mint_session(user: User) -> ApiResponse<UserSession> { unimplemented!() } - - // app handler — composes the library - pub fn handle_login( - req: LoginRequest, - ) -> ApiResponse<UserSession> { - and_then( - validate(req), - |creds| and_then( - fetch_user(creds), - |user| mint_session(user), - ), - ) - } - - - - .dag - - - - - - - - - // PRIMITIVE - type Sum<A, B> = - Left{value:A} | Right{value:B} - - // HELPERS - type Result<T, E> = - Sum<E, T> - - // LIBRARY - type ApiResponse<T> = - Result<T, HttpError> - type HttpError { - status: Int; body: Str - } - - // DOMAIN - type LoginRequest { - email: Str - password: Str - } - type Credentials { - email: Str; hash: Bytes - } - type User { - id: UserId; email: Str - } - type UserSession { - user_id: UserId - expires_at: Timestamp - } - - Same color = same concept across columns. Trace blue, green, purple, pink, amber, yellow — each one shows up exactly where it lives, on either wing or in the middle. + Four layers, color-coded — and the tests come along. + Each layer gets a color. Watch how each layer contributes facts, logic, or both — and how the middle materializes everything plus a test module. + + + + LAYER → WHERE IT APPEARS + + + + Primitive (Sum) + facts → generated · no logic at this layer + + + + Helpers (Result, map, and_then) + facts + logic → generated + + + + Library (ApiResponse, HttpError, fn sigs) + facts + logic → generated + + + + Domain (LoginRequest, …) + facts → generated + + + + App (handle_login) + logic → generated + + + + Tests (auto-emitted) + no source — emitted from the structural fold + + BUSINESS LOGIC · HELPERS · LIBRARIES + RUST — GENERATED + FACTS · STRUCTS · OBJECTS + + + + .dag + + + + + + + + + // helpers (operate on Sum) + fn map<A, B, C>( + s: Sum<A, B>, f: A -> C + ) -> Sum<C, B> = + match s { + Left { value } => + Left { value: f(value) } + Right { value } => Right { value } } + fn and_then<A, B, C>( + s: Sum<A, B>, f: A -> Sum<C, B> + ) -> Sum<C, B> = + match s { + Left { value } => f(value) + Right { value } => Right { value } } + + + // library — known signatures + fn validate( + req: LoginRequest + ) -> ApiResponse<Credentials> + fn fetch_user( + creds: Credentials + ) -> ApiResponse<User> + fn mint_session( + user: User + ) -> ApiResponse<UserSession> + + + // app — handler composes them + fn handle_login( + req: LoginRequest + ) -> ApiResponse<UserSession> = + and_then( + validate(req), + fn(creds) => + and_then( + fetch_user(creds), + fn(user) => mint_session(user))) + + + + RUST + + + + + + + + + + + + // primitive + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub enum Sum<A, B> { Left { value: A }, Right { value: B } } + impl<A, B> Sum<A, B> { + pub fn left(value: A) -> Self { Self::Left { value } } + pub fn right(value: B) -> Self { Self::Right { value } } + pub fn is_left(&self) -> bool { matches!(self, Self::Left {..}) } + pub fn is_right(&self) -> bool { matches!(self, Self::Right {..}) } + } + + + // helpers + pub type Result<T, E> = Sum<E, T>; + pub fn map<A, B, C>(s: Sum<A, B>, f: impl FnOnce(A) -> C) + -> Sum<C, B> { + match s { + Sum::Left { value } => Sum::Left { value: f(value) }, + Sum::Right { value } => Sum::Right { value }, + } + } + pub fn and_then<A, B, C>(s: Sum<A, B>, + f: impl FnOnce(A) -> Sum<C, B>) -> Sum<C, B> { + match s { Sum::Left{value}=>f(value), Sum::Right{value}=>Sum::Right{value} } + } + + + // library + pub type ApiResponse<T> = Result<T, HttpError>; + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub struct HttpError { pub status: i32, pub body: String } + impl HttpError { + pub fn new(status: i32, body: String) -> Self { Self { status, body } } + } + pub fn validate(req: LoginRequest) -> ApiResponse<Credentials> { unimplemented!() } + pub fn fetch_user(creds: Credentials) -> ApiResponse<User> { unimplemented!() } + pub fn mint_session(user: User) -> ApiResponse<UserSession> { unimplemented!() } + + + // domain + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub struct LoginRequest { pub email: String, pub password: String } + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub struct Credentials { pub email: String, pub hash: Vec<u8> } + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub struct User { pub id: UserId, pub email: String } + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub struct UserSession { pub user_id: UserId, pub expires_at: Timestamp } + impl LoginRequest { + pub fn new(email: String, password: String) -> Self { + Self { email, password } + } + } + + + // app handler — composes the library + pub fn handle_login( + req: LoginRequest, + ) -> ApiResponse<UserSession> { + and_then( + validate(req), + |creds| and_then( + fetch_user(creds), + |user| mint_session(user), + ), + ) + } + + + // tests — emitted from the structural fold + #[cfg(test)] + mod tests { + use super::*; + #[test] + fn sum_constructors_and_predicates() { + let l: Sum<i32, &str> = Sum::left(42); + let r: Sum<i32, &str> = Sum::right("hi"); + assert!(l.is_left() && !l.is_right()); + assert!(r.is_right() && !r.is_left()); + } + #[test] + fn map_transforms_left_branch() { + let s: Sum<i32, &str> = Sum::left(3); + assert_eq!(map(s, |n| n + 1), Sum::Left { value: 4 }); + } + #[test] + fn and_then_chains_left_branch() { + let s: Sum<i32, &str> = Sum::left(3); + assert_eq!(and_then(s, |n| Sum::left(n*2)), Sum::Left { value: 6 }); + } + } + + + + .dag + + + + + + + + + // PRIMITIVE + type Sum<A, B> = + Left{value:A} | Right{value:B} + + // HELPERS + type Result<T, E> = + Sum<E, T> + + // LIBRARY + type ApiResponse<T> = + Result<T, HttpError> + type HttpError { + status: Int; body: Str + } + + // DOMAIN + type LoginRequest { + email: Str + password: Str + } + type Credentials { + email: Str; hash: Bytes + } + type User { + id: UserId; email: Str + } + type UserSession { + user_id: UserId + expires_at: Timestamp + } + + Layers stack on the right. Logic on the left only at layers where you actually write code. The middle expands the lot — including a test module you didn't write. From 86f0f6115be9d8bd6cf36a9e8dcba059bed2e52b Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 6 May 2026 20:54:42 +0000 Subject: [PATCH 11/16] =?UTF-8?q?docs:=2004-colored=20=E2=80=94=20Path=20A?= =?UTF-8?q?=20from=20gunbc=20PM=20review=20(R3=20qualifier=20+=20auto=20ma?= =?UTF-8?q?rkers)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per gunbc PM review on PR #1879 (deep-wolf-155, 2026-05-06T20:46:43Z): within each layer-block the CENTER mixed three origin classes — direct mirror of .dag, user-authored .dag logic, and fold-rule auto-emit — all sharing one tint. Applying the recommended Path A: light-touch inline markers that flag the auto-emit lines, plus a load-bearing qualifier on the TESTS panel. Changes: - TESTS panel header: "// tests — R3 DELIVERABLE (T-Tests-As-Data lane; not emitted today)". Legend tests row updated to match. The panel previously claimed behavior that doesn't exist yet — verified by the PM that gunbc emits 0 #[cfg(test)] markers today and that R3 closure gates #74/#84/#85/#86/#87 will deliver this. - Inline "// auto — fold rule" annotations (italic gray, via tspan) on the three impl blocks that the structural fold emits without authored source: impl Sum (constructors + predicates), impl HttpError (new), impl LoginRequest (new). - Subtitle updated so the auto-marker convention is explained at the top of the diagram. Not applied (intentional, lighter touch): - #[derive(...)] left as plain code — deriving is universally understood as mechanical, the layer comment + auto-marker on the impl block is enough signal. - unimplemented!() left as plain code — body filler is obviously placeholder; over-marking would clutter. - Within-helpers fact-vs-logic split (Result alias vs map/and_then) not visually separated — the helpers layer comment will gain a small clarifier in a follow-up if it reads ambiguous. --- docs/demos/emission-intuition/04-colored.svg | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/demos/emission-intuition/04-colored.svg b/docs/demos/emission-intuition/04-colored.svg index 22d622bbba0..fbcc0ab7de0 100644 --- a/docs/demos/emission-intuition/04-colored.svg +++ b/docs/demos/emission-intuition/04-colored.svg @@ -30,7 +30,7 @@ Four layers, color-coded — and the tests come along. - Each layer gets a color. Watch how each layer contributes facts, logic, or both — and how the middle materializes everything plus a test module. + Each layer gets a color. Italic // auto markers in the middle flag lines that the structural fold emits without any .dag source. The TESTS panel is qualified for R3 — gunbc does not emit tests today. @@ -63,8 +63,8 @@ - Tests (auto-emitted) - no source — emitted from the structural fold + Tests (R3 deliverable) + not emitted today; T-Tests-As-Data lane will deliver BUSINESS LOGIC · HELPERS · LIBRARIES RUST — GENERATED @@ -137,7 +137,7 @@ // primitive #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Sum<A, B> { Left { value: A }, Right { value: B } } - impl<A, B> Sum<A, B> { + impl<A, B> Sum<A, B> { // auto — fold rule pub fn left(value: A) -> Self { Self::Left { value } } pub fn right(value: B) -> Self { Self::Right { value } } pub fn is_left(&self) -> bool { matches!(self, Self::Left {..}) } @@ -164,7 +164,8 @@ pub type ApiResponse<T> = Result<T, HttpError>; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct HttpError { pub status: i32, pub body: String } - impl HttpError { + impl HttpError { // auto — fold rule + pub fn new(status: i32, body: String) -> Self { Self { status, body } } } pub fn validate(req: LoginRequest) -> ApiResponse<Credentials> { unimplemented!() } @@ -181,7 +182,8 @@ pub struct User { pub id: UserId, pub email: String } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct UserSession { pub user_id: UserId, pub expires_at: Timestamp } - impl LoginRequest { + impl LoginRequest { // auto — fold rule + pub fn new(email: String, password: String) -> Self { Self { email, password } } @@ -202,7 +204,7 @@ } - // tests — emitted from the structural fold + // tests — R3 DELIVERABLE (T-Tests-As-Data lane; not emitted today) #[cfg(test)] mod tests { use super::*; From 8bc741bce86e137d60136a38c095772f11d213b0 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 6 May 2026 21:22:45 +0000 Subject: [PATCH 12/16] =?UTF-8?q?docs:=2002=20=E2=80=94=20close=20pub=20fn?= =?UTF-8?q?=20and=5Fthen=20properly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per codex BLOCKING inline review at 02:84 (sha 86f0f61): pub fn and_then was missing its outer closing brace — the snippet ended with the match's ` }` but never closed the function itself. Added the fn-close `}`, extended CENTER card height (568 → 600) to fit, shifted the closing sub line and viewBox to keep it below the card border. Swept all five SVGs for the same class of bug; brace-balance check shows every other pub fn is properly closed: python3 brace-balance check across all SVGs: 01-start-small 6 fns, all CLOSED 02-add-languages 7 fns, all CLOSED (post-fix) 03-add-types 10 fns, all CLOSED 04-now-scale 12 fns, all CLOSED 04-colored 12 fns, all CLOSED --- docs/demos/emission-intuition/02-add-languages.svg | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/demos/emission-intuition/02-add-languages.svg b/docs/demos/emission-intuition/02-add-languages.svg index 673d635a117..decfb2b8639 100644 --- a/docs/demos/emission-intuition/02-add-languages.svg +++ b/docs/demos/emission-intuition/02-add-languages.svg @@ -1,4 +1,4 @@ - + From dae47a893b49c4ecbf4cbc5a4b23351d6c1b891e Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 6 May 2026 21:26:23 +0000 Subject: [PATCH 13/16] =?UTF-8?q?docs:=2003/02/04/04-colored=20=E2=80=94?= =?UTF-8?q?=20flip=20Result=20alias=20for=20type-faithfulness?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per codex BLOCKING inline review at 03:88 (sha 86f0f61): with Result=Sum and and_then binding Sum::Left, the fetch_user composition's `|bytes| parse_user(bytes)` closure actually receives HttpError, not Bytes — INVARIANTS P1 (modeling-faithfulness) violation. Pick: flip the Result alias from Sum to Sum so success lands on Left where and_then/map already bind. Single edit per file, no helper signatures change. Trade-off: this picks "Left=success" as the convention, which is the inverse of Haskell-Either / Rust-Result. Going Option A (alias flip) over Option B (rewrite and_then/map to bind Right) because the latter is a 16-place rewrite across all 4 SVGs vs. the former being a single line per file. Reader sees the alias explicitly so the convention is readable; if a follow-up wants the conventional (Right-biased) shape, the existing Sum primitive supports it. Type-faithfulness check on 03 fetch_user: - Result = Sum → Left=T (success), Right=E (error) - ApiResponse = Sum - http_get(url): Sum - and_then(s: Sum, f: A -> Sum): binds Left - and_then(http_get(url), |bytes| parse_user(bytes)): A=Bytes, B=HttpError, closure binds Bytes — matches the `|bytes|` name; parse_user returns Sum matching Sum; result Sum = ApiResponse --- docs/demos/emission-intuition/02-add-languages.svg | 4 ++-- docs/demos/emission-intuition/03-add-types.svg | 4 ++-- docs/demos/emission-intuition/04-colored.svg | 4 ++-- docs/demos/emission-intuition/04-now-scale.svg | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/demos/emission-intuition/02-add-languages.svg b/docs/demos/emission-intuition/02-add-languages.svg index decfb2b8639..ec94b86e741 100644 --- a/docs/demos/emission-intuition/02-add-languages.svg +++ b/docs/demos/emission-intuition/02-add-languages.svg @@ -62,7 +62,7 @@ pub fn is_left(&self) -> bool { matches!(self, Self::Left {..}) } pub fn is_right(&self) -> bool { matches!(self, Self::Right {..}) } } - pub type Result<T, E> = Sum<E, T>; + pub type Result<T, E> = Sum<T, E>; pub fn unwrap_str(s: Sum<String, String>) -> String { match s { Sum::Left { value } => value, @@ -93,7 +93,7 @@ Left{value:A} | Right{value:B} // HELPERS (alias) type Result<T, E> = - Sum<E, T> + Sum<T, E> Two layers of facts. The helpers on the left know about the primitive — and they're enough to power everything that comes next. diff --git a/docs/demos/emission-intuition/03-add-types.svg b/docs/demos/emission-intuition/03-add-types.svg index cf2ace171dd..c5865263e7d 100644 --- a/docs/demos/emission-intuition/03-add-types.svg +++ b/docs/demos/emission-intuition/03-add-types.svg @@ -58,7 +58,7 @@ pub fn is_right(&self) -> bool { matches!(self, Self::Right {..}) } } // helpers — derived aliases + combinators - pub type Result<T, E> = Sum<E, T>; + pub type Result<T, E> = Sum<T, E>; pub fn map<A, B, C>(s: Sum<A, B>, f: impl FnOnce(A) -> C) -> Sum<C, B> { match s { @@ -98,7 +98,7 @@ Left{value:A} | Right{value:B} // HELPERS type Result<T, E> = - Sum<E, T> + Sum<T, E> // LIBRARY type ApiResponse<T> = Result<T, HttpError> diff --git a/docs/demos/emission-intuition/04-colored.svg b/docs/demos/emission-intuition/04-colored.svg index fbcc0ab7de0..29a8080aeab 100644 --- a/docs/demos/emission-intuition/04-colored.svg +++ b/docs/demos/emission-intuition/04-colored.svg @@ -146,7 +146,7 @@ // helpers - pub type Result<T, E> = Sum<E, T>; + pub type Result<T, E> = Sum<T, E>; pub fn map<A, B, C>(s: Sum<A, B>, f: impl FnOnce(A) -> C) -> Sum<C, B> { match s { @@ -244,7 +244,7 @@ // HELPERS type Result<T, E> = - Sum<E, T> + Sum<T, E> // LIBRARY type ApiResponse<T> = diff --git a/docs/demos/emission-intuition/04-now-scale.svg b/docs/demos/emission-intuition/04-now-scale.svg index a0837238d4b..f9150ab578e 100644 --- a/docs/demos/emission-intuition/04-now-scale.svg +++ b/docs/demos/emission-intuition/04-now-scale.svg @@ -64,7 +64,7 @@ pub fn is_right(&self) -> bool { matches!(self, Self::Right {..}) } } // helpers - pub type Result<T, E> = Sum<E, T>; + pub type Result<T, E> = Sum<T, E>; pub fn map<A, B, C>(s: Sum<A, B>, f: impl FnOnce(A) -> C) -> Sum<C, B> { match s { @@ -122,7 +122,7 @@ Left{value:A} | Right{value:B} // HELPERS type Result<T, E> = - Sum<E, T> + Sum<T, E> // LIBRARY type ApiResponse<T> = Result<T, HttpError> From 35c5b722dec9de33a0705e1b21fabbd2927698e9 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 6 May 2026 22:15:11 +0000 Subject: [PATCH 14/16] =?UTF-8?q?docs:=2004=20=E2=80=94=20inline=20UserId/?= =?UTF-8?q?Timestamp=20+=20soften=20"ready=20to=20run"=20claim?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per codex BLOCKING inline review at 04-now-scale:148 (and the parallel finding on 04-colored): "ready to run" punchline overclaimed against unimplemented!() library bodies and undeclared UserId/Timestamp carriers. Two-part fix: 1. Inline the carriers so nothing is undeclared. UserId → u64 in Rust / Int in .dag; Timestamp → i64 in Rust / Int in .dag. Affected: User { pub id: u64, ... }, UserSession { pub user_id: u64, pub expires_at: i64 }, plus the matching .dag fact lines. 2. Soften the 04-now-scale punchline from "ready to run" to "ready to compile (drop in the library bodies and it runs)" so the slide doesn't claim runtime correctness while the library functions are still scaffolded with unimplemented!(). 04-colored's punchline already says "the middle expands the lot" — no claim about running, no change needed. Modules now compile end-to-end (modulo the explicitly-elided library bodies). --- docs/demos/emission-intuition/04-colored.svg | 10 +++++----- docs/demos/emission-intuition/04-now-scale.svg | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/demos/emission-intuition/04-colored.svg b/docs/demos/emission-intuition/04-colored.svg index 29a8080aeab..ddad9cfb05f 100644 --- a/docs/demos/emission-intuition/04-colored.svg +++ b/docs/demos/emission-intuition/04-colored.svg @@ -179,9 +179,9 @@ #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Credentials { pub email: String, pub hash: Vec<u8> } #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub struct User { pub id: UserId, pub email: String } + pub struct User { pub id: u64, pub email: String } #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub struct UserSession { pub user_id: UserId, pub expires_at: Timestamp } + pub struct UserSession { pub user_id: u64, pub expires_at: i64 } impl LoginRequest { // auto — fold rule pub fn new(email: String, password: String) -> Self { @@ -262,11 +262,11 @@ email: Str; hash: Bytes } type User { - id: UserId; email: Str + id: Int; email: Str } type UserSession { - user_id: UserId - expires_at: Timestamp + user_id: Int + expires_at: Int } Layers stack on the right. Logic on the left only at layers where you actually write code. The middle expands the lot — including a test module you didn't write. diff --git a/docs/demos/emission-intuition/04-now-scale.svg b/docs/demos/emission-intuition/04-now-scale.svg index f9150ab578e..b356adf8bf1 100644 --- a/docs/demos/emission-intuition/04-now-scale.svg +++ b/docs/demos/emission-intuition/04-now-scale.svg @@ -89,9 +89,9 @@ #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Credentials { pub email: String, pub hash: Vec<u8> } #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub struct User { pub id: UserId, pub email: String } + pub struct User { pub id: u64, pub email: String } #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub struct UserSession { pub user_id: UserId, pub expires_at: Timestamp } + pub struct UserSession { pub user_id: u64, pub expires_at: i64 } impl LoginRequest { pub fn new(email: String, password: String) -> Self { Self { email, password } @@ -138,12 +138,12 @@ email: Str; hash: Bytes } type User { - id: UserId; email: Str + id: Int; email: Str } type UserSession { - user_id: UserId - expires_at: Timestamp + user_id: Int + expires_at: Int } - Four stacked layers on the right. The handler on the left only writes against the top two. The middle has the whole stack built and ready to run. + Four stacked layers on the right. The handler on the left only writes against the top two. The middle is the whole stack — types, helpers, library scaffolding, app body — ready to compile (drop in the library bodies and it runs). From 8f07847bed10b4891edf0dd3fa9051698678203b Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 6 May 2026 23:48:32 +0000 Subject: [PATCH 15/16] =?UTF-8?q?docs:=2003=20=E2=80=94=20declare=20User/B?= =?UTF-8?q?ytes;=2004=20=E2=80=94=20inline=20Bytes=20in=20.dag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per codex BLOCKING inline review at 03-add-types.svg:84 (sha 35c5b72): User and UserId referenced in 03 without any declared fact in the FACTS column — every emitted identifier must flow from a source fact (THESIS mechanical emission). Fixes: 03-add-types.svg: - Inline UserId → Int (.dag) / u64 (Rust) — too granular for the 3-layer narrative; .dag uses generic Int. - Add User and Bytes declarations to FACTS LIBRARY layer: type User { id: Int } type Bytes = Vec Card height extended 318 → 380. - Add corresponding emission to CENTER LIBRARY section: pub struct User { pub id: u64 } pub type Bytes = Vec; Library fns + fetch_user impl shifted down by 46px. 04-now-scale.svg / 04-colored.svg: - Inline `hash: Bytes` in .dag Credentials struct → `hash: Vec` to match the Rust emission (which already used Vec after the earlier 35c5b72 carrier inline). Keeps the .dag/Rust pair internally consistent — no .dag identifier without a declaration. --- .../demos/emission-intuition/03-add-types.svg | 24 +++++++++++-------- docs/demos/emission-intuition/04-colored.svg | 2 +- .../demos/emission-intuition/04-now-scale.svg | 2 +- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/docs/demos/emission-intuition/03-add-types.svg b/docs/demos/emission-intuition/03-add-types.svg index c5865263e7d..9305c56c3e1 100644 --- a/docs/demos/emission-intuition/03-add-types.svg +++ b/docs/demos/emission-intuition/03-add-types.svg @@ -35,7 +35,7 @@ ) -> ApiResponse<User> // composes the library fn fetch_user( - id: UserId + id: Int ) -> ApiResponse<User> = and_then( http_get(format("/u/{id}")), @@ -80,19 +80,21 @@ impl HttpError { pub fn new(status: i32, body: String) -> Self { Self { status, body } } } - pub fn http_get(url: &str) -> ApiResponse<Bytes> { unimplemented!() } - pub fn parse_user(bytes: Bytes) -> ApiResponse<User> { unimplemented!() } - pub fn fetch_user(id: UserId) -> ApiResponse<User> { - and_then( - http_get(&format!("/u/{}", id)), - |bytes| parse_user(bytes), - ) - } + pub struct User { pub id: u64 } + pub type Bytes = Vec<u8>; + pub fn http_get(url: &str) -> ApiResponse<Bytes> { unimplemented!() } + pub fn parse_user(bytes: Bytes) -> ApiResponse<User> { unimplemented!() } + pub fn fetch_user(id: u64) -> ApiResponse<User> { + and_then( + http_get(&format!("/u/{}", id)), + |bytes| parse_user(bytes), + ) + } .dag - + // PRIMITIVE type Sum<A, B> = Left{value:A} | Right{value:B} @@ -106,6 +108,8 @@ status: Int body: Str } + type User { id: Int } + type Bytes = Vec<u8> Three stacked layers on the right. Library code on the left only mentions the top one. Everything underneath still gets emitted. diff --git a/docs/demos/emission-intuition/04-colored.svg b/docs/demos/emission-intuition/04-colored.svg index ddad9cfb05f..086f5cd5dce 100644 --- a/docs/demos/emission-intuition/04-colored.svg +++ b/docs/demos/emission-intuition/04-colored.svg @@ -259,7 +259,7 @@ password: Str } type Credentials { - email: Str; hash: Bytes + email: Str; hash: Vec<u8> } type User { id: Int; email: Str diff --git a/docs/demos/emission-intuition/04-now-scale.svg b/docs/demos/emission-intuition/04-now-scale.svg index b356adf8bf1..89122276faa 100644 --- a/docs/demos/emission-intuition/04-now-scale.svg +++ b/docs/demos/emission-intuition/04-now-scale.svg @@ -135,7 +135,7 @@ password: Str } type Credentials { - email: Str; hash: Bytes + email: Str; hash: Vec<u8> } type User { id: Int; email: Str From 444ad4853dd68bee0b7cd976c1ccae2885096239 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 7 May 2026 00:21:58 +0000 Subject: [PATCH 16/16] =?UTF-8?q?docs:=20target-grounding=20=E2=80=94=20st?= =?UTF-8?q?rip=20Rust=20realizations=20from=20.dag=20side?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per codex BLOCKING inline review at 03-add-types.svg:112 (sha 8f07847): the .dag facts column had `type Bytes = Vec`, treating a Rust-specific realization as if it were a grounded source fact. THESIS violation — .dag declares structural facts; LangSpec realizes them per target. Fixes: 03-add-types.svg: - FACTS column: `type Bytes = Vec` → `type Bytes // built-in`. The .dag now declares Bytes as an opaque primitive; the Rust LangSpec emits `pub type Bytes = Vec;` in the CENTER column as its target realization (kept as-is — that's the Rust column, Rust types are appropriate there). 04-now-scale.svg / 04-colored.svg: - Same class of bug after the earlier inline of `hash: Bytes` → `hash: Vec` on the .dag-side Credentials struct. Reverted more carefully: `hash: Vec` → `hash: Str` (use a target- neutral primitive that's already in .dag's vocabulary). Rust side correspondingly: `pub hash: Vec` → `pub hash: String`. Avoids needing a separate `type Bytes` declaration in 04 and the layout shift that would entail. Verification: no Vec token remains in any .dag-side cell; all Vec occurrences are confined to Rust pub-type/pub-struct declarations in CENTER. --- docs/demos/emission-intuition/03-add-types.svg | 2 +- docs/demos/emission-intuition/04-colored.svg | 4 ++-- docs/demos/emission-intuition/04-now-scale.svg | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/demos/emission-intuition/03-add-types.svg b/docs/demos/emission-intuition/03-add-types.svg index 9305c56c3e1..ab0242f4137 100644 --- a/docs/demos/emission-intuition/03-add-types.svg +++ b/docs/demos/emission-intuition/03-add-types.svg @@ -109,7 +109,7 @@ body: Str } type User { id: Int } - type Bytes = Vec<u8> + type Bytes // built-in Three stacked layers on the right. Library code on the left only mentions the top one. Everything underneath still gets emitted. diff --git a/docs/demos/emission-intuition/04-colored.svg b/docs/demos/emission-intuition/04-colored.svg index 086f5cd5dce..3adf1cb28f0 100644 --- a/docs/demos/emission-intuition/04-colored.svg +++ b/docs/demos/emission-intuition/04-colored.svg @@ -177,7 +177,7 @@ #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct LoginRequest { pub email: String, pub password: String } #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub struct Credentials { pub email: String, pub hash: Vec<u8> } + pub struct Credentials { pub email: String, pub hash: String } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct User { pub id: u64, pub email: String } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -259,7 +259,7 @@ password: Str } type Credentials { - email: Str; hash: Vec<u8> + email: Str; hash: Str } type User { id: Int; email: Str diff --git a/docs/demos/emission-intuition/04-now-scale.svg b/docs/demos/emission-intuition/04-now-scale.svg index 89122276faa..9eaeb977c43 100644 --- a/docs/demos/emission-intuition/04-now-scale.svg +++ b/docs/demos/emission-intuition/04-now-scale.svg @@ -87,7 +87,7 @@ #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct LoginRequest { pub email: String, pub password: String } #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub struct Credentials { pub email: String, pub hash: Vec<u8> } + pub struct Credentials { pub email: String, pub hash: String } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct User { pub id: u64, pub email: String } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -135,7 +135,7 @@ password: Str } type Credentials { - email: Str; hash: Vec<u8> + email: Str; hash: Str } type User { id: Int; email: Str