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..c02591c0078 --- /dev/null +++ b/docs/demos/emission-intuition/01-start-small.svg @@ -0,0 +1,68 @@ + + + + + + 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 + + // 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 }, + 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 + + // PRIMITIVE + type Sum<A, B> = + Left{value:A} | Right{value:B} + + 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 new file mode 100644 index 00000000000..ec94b86e741 --- /dev/null +++ b/docs/demos/emission-intuition/02-add-languages.svg @@ -0,0 +1,99 @@ + + + + + + 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 + + // primitive consumer + fn unwrap_str( + s: Sum<Str, Str>) -> Str = + match s { + Left { value } => value + Right { value } => value + } + // generic helpers (still 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 } } + + + + 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<T, E>; + 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 + + // PRIMITIVE + type Sum<A, B> = + Left{value:A} | Right{value:B} + // HELPERS (alias) + type Result<T, E> = + 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 new file mode 100644 index 00000000000..ab0242f4137 --- /dev/null +++ b/docs/demos/emission-intuition/03-add-types.svg @@ -0,0 +1,115 @@ + + + + + + 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 + + // 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: Int + ) -> 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 + + // 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<T, E>; + 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 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} + // HELPERS + type Result<T, E> = + Sum<T, E> + // LIBRARY + type ApiResponse<T> = + Result<T, HttpError> + type HttpError { + status: Int + body: Str + } + type User { id: Int } + 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 new file mode 100644 index 00000000000..3adf1cb28f0 --- /dev/null +++ b/docs/demos/emission-intuition/04-colored.svg @@ -0,0 +1,273 @@ + + + + + + Four layers, color-coded — and the tests come along. + 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. + + + + 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 (R3 deliverable) + not emitted today; T-Tests-As-Data lane will deliver + + 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> { // 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 {..}) } + pub fn is_right(&self) -> bool { matches!(self, Self::Right {..}) } + } + + + // helpers + 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 { + 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 { // auto — fold rule + + 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: String } + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub struct User { pub id: u64, pub email: String } + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub struct UserSession { pub user_id: u64, pub expires_at: i64 } + impl LoginRequest { // auto — fold rule + + 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 — R3 DELIVERABLE (T-Tests-As-Data lane; not emitted today) + #[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<T, E> + + // 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: Str + } + type User { + id: Int; email: Str + } + type UserSession { + 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 new file mode 100644 index 00000000000..9eaeb977c43 --- /dev/null +++ b/docs/demos/emission-intuition/04-now-scale.svg @@ -0,0 +1,149 @@ + + + + + + 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 + + // 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<T, E>; + 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: String } + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub struct User { pub id: u64, pub email: String } + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub struct UserSession { pub user_id: u64, pub expires_at: i64 } + 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<T, E> + // 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: Str + } + type User { + id: Int; email: Str + } + type UserSession { + 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 is the whole stack — types, helpers, library scaffolding, app body — ready to compile (drop in the library bodies and it runs). +