Algebraic Foundations
Morph is built on Lawvere’s functorial semantics: a domain schema defines an algebraic theory, and each generated package is a functor from that theory into a target category of programs. App adapters are natural transformations between these functors.
Conceptual Overview
flowchart TB
subgraph Theory["Theory T (Schema)"]
sorts["Sorts: entities, value objects, types"]
ops["Operations: commands, queries, functions"]
eqns["Equations: invariants, examples"]
end
subgraph Functors["Algebras (Enriched Functors T → Eff)"]
direction TB
subgraph Free["F_dsl — Free Algebra (DSL)"]
schemas["Effect Schema types"]
opDefs["Operation definitions (data)"]
errors["Error types"]
end
subgraph Concrete["F_core — Concrete Algebra (Core)"]
handlers["Operation handlers"]
impls["Implementations"]
repos["Repository backends"]
end
subgraph Derived["F_api, F_cli, F_mcp, ... — Derived Algebras (Apps)"]
api["API: routes + JSON"]
cli["CLI: commands + argv"]
mcp["MCP: tools + params"]
ui["UI: components + actions"]
end
end
subgraph Projections["Projections (from Free Algebra)"]
diagrams["ERD diagrams"]
types["Type definitions"]
validators["Validators"]
mocks["Mock implementations"]
prose["Prose templates"]
end
subgraph Enrichment["Enrichment (Effect<A, E, R>)"]
direction LR
A["A — base result"]
E["E — error algebra"]
R["R — capabilities: auth, storage, events"]
end
Theory --> Functors
Free --> Projections
Free -.->|"unique homomorphism (initiality)"| Concrete
Concrete -.->|"η: natural transformations"| Derived
Enrichment -.->|"enriches"| Functors
style Theory fill:#e1f5fe
style Free fill:#f3e5f5
style Concrete fill:#e8f5e9
style Derived fill:#fce4ec
style Projections fill:#fff3e0
style Enrichment fill:#f5f5f5
Theory T: The Schema as a Category
A domain schema defines a multi-sorted algebraic theory — a small category T where:
- Objects are sorts: entities, value objects, type aliases
- Morphisms are operations: typed arrows from input to output
- Equations are invariants: commuting conditions that valid interpretations must satisfy
| Schema Element | Role in T |
|---|---|
entities |
Sorts (carrier sets with identity) |
valueObjects |
Product types (carrier sets without identity) |
types, aliases, unions |
Type constructors (products, coproducts, aliases) |
commands |
Morphisms with effects (state mutation, event emission) |
queries |
Morphisms without effects (observations) |
functions |
Pure morphisms (no state, no effects) |
invariants |
Equations: conditions that must hold across all valid interpretations |
errors |
Partiality: operations are partial morphisms with typed failure cases |
The key distinction: commands mutate state (they change the carrier set’s elements), queries observe state (read-only morphisms), and functions are pure (no state at all — genuine morphisms in the mathematical sense).
Algebras as Enriched Functors
In Lawvere’s framework, an algebra (or model) of a theory T is a structure-preserving functor from T into some target category. It maps each sort to a concrete type and each operation to a concrete program.
Morph’s algebras are enriched functors F: T → Eff, where Eff is the category of Effect computations. The enrichment matters: Effect’s type signature Effect<A, E, R> encodes three layers of structure.
| Parameter | What it encodes | Categorical role |
|---|---|---|
A |
Result value | Base algebra: sorts → types, operations → programs |
E |
Error types | Partiality: operations are partial morphisms with typed failure |
R |
Requirements | Capability grading: which services the operation needs |
Each algebra maps theory T into Eff differently:
| Algebra | Functor | What it maps sorts to | What it maps operations to |
|---|---|---|---|
| DSL | F_dsl | Effect Schema types | OperationCall descriptions (data) |
| Core | F_core | Concrete types | Effect programs (with handlers) |
| API | F_api | JSON schemas | HTTP route handlers |
| CLI | F_cli | Display formats | Shell commands |
| MCP | F_mcp | Tool parameter schemas | Tool handlers |
| Mocks | F_mock | Fast-check arbitraries | Mock implementations |
All these functors target Eff — the app-specific categories (HTTP routes, shell commands, tool handlers) are subcategories of Effect computations. An HTTP route handler is an Effect program that additionally processes request/response. A CLI command is an Effect program that additionally parses argv.
The Free Algebra: DSL as Initial Object
The DSL functor F_dsl is the initial object (free algebra) in the category of T-algebras. This means: for any other algebra F, there exists a unique homomorphism from F_dsl to F.
Concretely:
- Operations are represented as data (
OperationCalldescriptions), not executions - No implementation choices are made — the DSL captures pure structure
- Any other interpretation can be derived from it
Initiality is why projections work. Diagrams, validators, mocks, and prose templates are all computable from F_dsl’s structure, because the free algebra makes the maximum amount of structural information available.
| Projection | What it computes from F_dsl |
|---|---|
| ERD diagrams | Entity relationships from sort references |
| Type definitions | TypeScript types from Effect Schema |
| Validators | Runtime validation from schema constraints |
| Repository interfaces | Ports from entity sorts |
| Mock implementations | Fast-check arbitraries from schema structure |
| Prose templates | Natural language from operation names and types |
Concrete Algebras
The core functor F_core is a non-free algebra — it makes specific implementation choices beyond what the theory requires:
- Handlers implement how operations execute
- Repository backends choose where data lives
- Business logic satisfies the theory’s equations (invariants)
The mock algebra F_mock is another non-free algebra: fast-check arbitraries generate valid instances, and mock handlers produce plausible results. It’s useful for property testing — a different interpretation of the same theory.
Natural Transformations: Apps as Homomorphisms
A natural transformation η: F ⇒ G between two algebras (functors) is a family of morphisms, one per object of T, that commutes with the algebra structure. In Lawvere’s framework, this is exactly an algebra homomorphism.
The app adapters are natural transformations from F_core to derived algebras:
- η_api: F_core ⇒ F_api — wraps each core handler as an HTTP route
- η_cli: F_core ⇒ F_cli — wraps each core handler as a CLI command
- η_mcp: F_core ⇒ F_mcp — wraps each core handler as an MCP tool
For each sort (entity/type) X in T, the component η_X maps F_core(X) to F_api(X) — the core type to its API representation.
Naturality condition: for any operation f: X → Y in T:
η_Y ∘ F_core(f) = F_api(f) ∘ η_X
In words: applying the core operation then adapting the result to HTTP must equal adapting the input to HTTP then applying the API operation. The order doesn’t matter — the diagram commutes.
This is what “structure-preserving” means concretely: a schema command becomes a core handler, which becomes a POST endpoint, a CLI command, an MCP tool, and a client method. The algebraic structure is preserved through every transformation because each adapter is a natural transformation, not an ad hoc wrapper.
The Enrichment: Auth, Events, and Layers
Cross-cutting concerns (auth, storage, events) aren’t ad hoc additions — they’re part of the enriched functor’s structure, encoded in Effect’s R (requirements) parameter.
Capabilities as Grading
An operation’s R type grades it by the capabilities it needs. The generator inspects the theory (schema invariants) and determines the grade:
- An invariant referencing
context.currentUseradds auth capabilities to R (inferred from invariant expressions) - Entity persistence adds per-entity repository services to R (e.g.,
TodoRepository,UserRepository) - Event emission adds
EventEmitterto R
This grading is intrinsic to the enriched functor — it’s not a meta-level escape from the algebra, it’s part of what the functor maps each operation to.
Extensions as Sub-theory Algebras
The storage interface (get, put, delete, list) is itself a small algebraic theory. Each backend is a different algebra of that sub-theory:
| Sub-theory | Algebras (backends) |
|---|---|
| Storage | Memory, JSONFile, SQLite, Redis |
| Auth | Password, JWT, API key, Session, None |
| Event store | Memory, JSONFile, Redis |
Choosing a backend is choosing an algebra for a sub-theory. The main algebra Fcore _depends on these sub-theory choices but is parametric over them — any valid storage algebra satisfies the same interface.
Layers as a Monoidal Category
Effect Layers compose the capability algebras into a complete runtime environment. This composition forms a symmetric monoidal category:
- Objects: requirement types (the
RinEffect<A, E, R>) - Morphisms: layers (
Layer<ROut, E, RIn>— “given RIn, provide ROut”) - Tensor:
Layer.mergeAll(provide multiple capabilities: R_1 ⊗ R_2) - Unit:
Layer.empty(no requirements)
Effect.provide(layer) is the evaluation morphism — it eliminates requirements by supplying concrete implementations. The full app layer (AppLayer = AuthLayer | StorageLayer | EventLayer) is a tensor product in this monoidal category, composing all the sub-theory algebra choices into a single runtime.
Verifying Algebraic Laws
Morph verifies that algebras satisfy the theory’s laws at three levels — scenarios (example-based), property tests (probabilistic), and formal verification (exhaustive). Each level corresponds to a different kind of evidence about the algebraic structure.
Scenarios Verify Naturality
Gherkin scenarios are equations in the theory T. Each scenario asserts: given these preconditions, this operation produces this result.
Running a scenario against interpretation F means applying F to the equation and checking it holds. The step definitions for each target runner (core, api, cli, mcp) are the components of the natural transformation — they map abstract scenario steps to concrete actions in each interpretation.
| Component | Algebraic role |
|---|---|
| Scenario | Equation in T |
| Core step defs | Components of F_core applied to the equation |
| API step defs | Components of η_api ∘ F_core applied to the equation |
| CLI step defs | Components of η_cli ∘ F_core applied to the equation |
| All runners pass | Evidence that η preserves equations (naturality holds) |
If the same scenario passes for F_core and F_api, this provides evidence that η_api is a valid natural transformation — it preserves the algebraic laws. If core passes but API fails, the API generator has a bug: the natural transformation doesn’t commute for that equation.
Property Tests Verify Invariants Probabilistically
Property-based tests (fast-check) verify that the algebra’s universal laws — invariants that must hold for all inputs — are satisfied by randomly sampling the input space. The same ConditionExpr AST that defines invariants in the schema is compiled to JavaScript predicates and tested against schema-derived arbitraries.
| Suite type | What it verifies |
|---|---|
| Validator | Guard functions correctly implement invariant predicates |
| Operation | Operation execution respects pre/post invariants across random inputs |
| Contract | Port implementations (storage, auth, events) satisfy algebraic laws |
Contract suites are particularly relevant: they verify that sub-theory algebras (storage backends, auth backends) satisfy the same interface laws regardless of implementation. PutGetRoundtrip, RemoveIdempotent, and similar laws hold for memory, JSON file, SQLite, and Redis backends alike — evidence that backend choice doesn’t break the algebra.
Formal Verification Proves Invariants Exhaustively
The verification target generates SMT-LIB2 checks from the same ConditionExpr AST — same source, different compilation backend. Z3 proves properties for all inputs, not just sampled ones:
| Check | What it proves |
|---|---|
| Consistency | Entity invariants don’t contradict each other |
| Precondition satisfiability | Each operation’s preconditions can actually be satisfied (not dead code) |
| Preservation | Operations preserve entity invariants: pre ∧ post → invariant |
The invariant fragment (QF_UFLIA with bounded quantifier macros) is decidable — Z3 terminates with a definitive answer. When verification fails, Z3 produces a concrete counterexample. See Formal Verification for the full specification.
Limitations of the Analogy
Two qualifications:
Target categories are informal. “Eff” as a category of Effect computations doesn’t have a standard mathematical definition. The objects are TypeScript types, the morphisms are Effect programs, and composition is Effect.flatMap / pipe. This is workable as a design language — it guides architecture and catches structural errors. The verification target partially closes this gap: invariants expressed as ConditionExpr are compiled to SMT-LIB2 and checked by Z3, giving formal proofs for the decidable fragment (QF_UFLIA). This covers consistency, satisfiability, and preservation of entity invariants — but not the full behavior of Effect programs, which remains outside formal reach.
Generator meta-level. The generators themselves (the build-time code that reads schemas and emits TypeScript) operate at a meta-level above the algebra. The generator is a function from theories to code, not a functor within a single theory. This is analogous to how a compiler is not a program in the language it compiles. The algebraic model describes the generated code and its relationships, not the generation process itself.
Why This Matters
- Single source of truth: The schema is the theory. Everything else is a functor from it.
- Correctness by construction: Natural transformations preserve algebraic structure — if the core is correct, derived apps are correct.
- Separation of concerns: The free algebra (DSL) separates what from how. The enrichment separates capabilities from implementations.
- Testability: Scenarios are equations. Running them against multiple interpretations tests naturality.
- Extensibility: New apps are new natural transformations. New backends are new sub-theory algebras. The framework is open at the adapter level while closed at the algebraic level.