https://github.com/ibrahimcesar/category-theory-for-the-javascript-typescript-developers
π’ Category Theory for JavaScript/TypeScript developers. An embedded DSL making functors, monads, and adjunctions explicit and manipulable.
https://github.com/ibrahimcesar/category-theory-for-the-javascript-typescript-developers
adjunctions category-theory category-theory-for-programmers composition dsl educational fp-ts functional-programming functor javascript monad typescript
Last synced: about 2 months ago
JSON representation
π’ Category Theory for JavaScript/TypeScript developers. An embedded DSL making functors, monads, and adjunctions explicit and manipulable.
- Host: GitHub
- URL: https://github.com/ibrahimcesar/category-theory-for-the-javascript-typescript-developers
- Owner: ibrahimcesar
- License: mit
- Created: 2025-11-27T11:32:52.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2025-11-27T14:51:59.000Z (4 months ago)
- Last Synced: 2026-01-18T02:47:22.822Z (2 months ago)
- Topics: adjunctions, category-theory, category-theory-for-programmers, composition, dsl, educational, fp-ts, functional-programming, functor, javascript, monad, typescript
- Language: TypeScript
- Homepage: https://ibrahimcesar.cloud/blog/category-theory-for-javascript-typescript-developers
- Size: 82 KB
- Stars: 2
- Watchers: 0
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# π’ Category Theory for the JavaScript Developer
**An embedded DSL that makes categorical structure explicit and manipulable in TypeScript/JavaScript.**
[Core Concepts](#core-concepts) | [Practical payoffs](#practical-payoffs) | [Usage](#usage) | [Blog post](https://ibrahimcesar.cloud/blog/category-theory-for-javascript-typescript-developers)
## Why?
[Category Theory for Programmers](https://bartoszmilewski.com/2014/10/28/category-theory-for-programmers-the-preface/) by Bartosz Milewski is excellent but leans heavily on Haskell. This project bridges that gap for the JS/TS ecosystem.
**The goal isn't to write category theory proofs in production code.** It's to develop intuition for compositional patterns you already use dailyβand to know when they'll compose predictably, and when they won't.
## What You'll Learn
- **Monads aren't magic**βthey're what you get when you have a round-trip (adjunction) and compose the two functors
- **Promise.then is flatMap**, and the reason it "feels right" is because it satisfies the monad laws
- **The laws aren't arbitrary**βthey're the triangle identities of the underlying adjunction, bubbling up
- Why **async/await "infects"** your codebase (hint: it's an imperfect adjunction)
## Core Concepts
### Morphisms as First-Class Values
```typescript
interface Morphism {
source: string; // for debugging/visualization
target: string;
apply: (a: A) => B;
}
const compose = (
g: Morphism,
f: Morphism
): Morphism => ({
source: f.source,
target: g.target,
apply: (a) => g.apply(f.apply(a))
});
```
### Functors as Explicit Mappings
```typescript
interface Functor {
fmap: (f: Morphism) => Morphism, F>;
}
const ArrayFunctor: Functor = {
fmap: (f) => ({
source: `Array<${f.source}>`,
target: `Array<${f.target}>`,
apply: (arr) => arr.map(f.apply)
})
};
```
### Adjunctions: Where Monads Come From
```typescript
interface Adjunction {
left: Functor; // F: C β D (left adjoint)
right: Functor; // G: D β C (right adjoint)
unit: (a: A) => G>; // Ξ·: A β G(F(A))
counit: (fgb: F>) => B; // Ξ΅: F(G(B)) β B
}
```
### Monads Emerge Naturally
```typescript
const monadFromAdjunction = (adj: Adjunction) => ({
// M = G>
pure: (a: A) => adj.unit(a),
join: (mma: G>>>): G> =>
adj.right.fmap({ apply: adj.counit }).apply(mma),
flatMap: (ma: G>, f: (a: A) => G>) =>
join(adj.right.fmap(adj.left.fmap({ apply: f })).apply(ma))
});
```
## Practical Payoffs
### Before: Nested Null Checks
```typescript
function processUser(id: string) {
const user = getUser(id);
if (user === null) return null;
const profile = getProfile(user.profileId);
if (profile === null) return null;
return formatOutput(user, profile);
}
```
### After: Monadic Composition
```typescript
const processUser = (id: string) =>
getUser(id)
.flatMap(user => getProfile(user.profileId))
.map(profile => formatOutput(profile));
```
The monad laws **guarantee** safe refactoring. You're relying on mathematics, not convention.
## Project Structure
```
src/
βββ core/ # Core categorical abstractions
β βββ morphism.ts # Morphism type, composition, identity
β βββ functor.ts # Functor interface (Array, Option, Identity)
β βββ natural.ts # Natural transformations (head, last, flatten, etc.)
β βββ adjunction.ts # Adjunction interface and triangle identities
β βββ monad.ts # Monad (Array, Option, Promise) + Kleisli composition
β βββ yoneda.ts # Yoneda lemma, Coyoneda, Continuations (CPS)
β βββ index.ts
βββ instances/ # Additional monad implementations
β βββ either.ts # Either - typed error handling (Left/Right)
β βββ reader.ts # Reader - dependency injection pattern
β βββ state.ts # State - stateful computations
β βββ writer.ts # Writer - logging/accumulation with Monoid
β βββ io.ts # IO - encapsulating side effects
β βββ index.ts
βββ laws/ # Law verification functions
β βββ functor-laws.ts # Identity and composition laws
β βββ monad-laws.ts # Left/right identity, associativity
β βββ natural-laws.ts # Naturality condition
β βββ adjunction-laws.ts # Triangle identities
β βββ index.ts
βββ examples/ # Practical demonstrations
β βββ option-chaining.ts # Safe navigation, Kleisli composition
β βββ async-composition.ts # Promise composition, TaskEither, retry
β βββ state-monad.ts # Counter, stack, RNG, game state, parser
β βββ index.ts
βββ index.ts # Main entry point with all exports
```
## Installation
```bash
npm install
npm run build
npm test
```
## Usage
```typescript
import {
// Core
morphism, compose, identity, pipe,
// Option monad
some, none, isSome, OptionMonad,
// Either monad
left, right, isRight, EitherMonad,
// Other monads
ReaderMonad, StateMonad, IOMonad,
// Law verification
checkFunctorLaws, checkMonadLaws,
} from 'category-theory-js';
// Create morphisms with explicit types
const double = morphism('number', 'number', (x: number) => x * 2);
const toString = morphism('number', 'string', (x: number) => `Value: ${x}`);
// Compose them (right-to-left, mathematical style)
const doubleAndStringify = compose(toString, double);
console.log(doubleAndStringify.apply(21)); // "Value: 42"
// Option monad - safe null handling
const safeDivide = (a: number, b: number) =>
b === 0 ? none : some(a / b);
const result = OptionMonad.flatMap(
OptionMonad.flatMap(some(10), x => safeDivide(x, 2)),
x => safeDivide(x, 5)
);
console.log(result); // { _tag: 'Some', value: 1 }
// Either monad - typed error handling
const parseNumber = (s: string) => {
const n = parseFloat(s);
return isNaN(n) ? left('Not a number') : right(n);
};
const parsed = EitherMonad.flatMap(
parseNumber('42'),
n => right(n * 2)
);
console.log(parsed); // { _tag: 'Right', right: 84 }
```
## Available Monads
| Monad | Type | Use Case |
|-------|------|----------|
| **Option** | `Option` | Nullable values, safe property access |
| **Either** | `Either` | Typed error handling, validation |
| **Reader** | `Reader` | Dependency injection, configuration |
| **State** | `State` | Stateful computations, parsing |
| **Writer** | `Writer` | Logging, accumulating output |
| **IO** | `IO` | Side effects, lazy evaluation |
| **Promise** | `Promise` | Async operations (built-in) |
| **Array** | `A[]` | Non-determinism, multiple results |
## The Laws (Testable!)
```typescript
import {
checkFunctorLaws,
checkMonadLaws,
verifyNaturality,
checkAdjunctionLaws,
} from 'category-theory-js';
// Verify functor laws for Array
const functorResult = checkFunctorLaws(
'Array',
{ fmap: (arr, f) => arr.map(f) },
[
{ value: [1, 2, 3], f: (x: number) => x * 2, g: (x: number) => x + 1 }
]
);
console.log(functorResult.passed); // true
// Verify monad laws for Option
const monadResult = checkMonadLaws(
'Option',
OptionMonad,
[{
value: 5,
monadicValue: some(5),
f: (x: number) => some(x * 2),
g: (x: number) => some(x + 1),
}]
);
console.log(monadResult.passed); // true
// Results include: Left Identity, Right Identity, Associativity
```
### The Three Monad Laws
1. **Left Identity**: `pure(a).flatMap(f) === f(a)`
2. **Right Identity**: `m.flatMap(pure) === m`
3. **Associativity**: `m.flatMap(f).flatMap(g) === m.flatMap(x => f(x).flatMap(g))`
## Yoneda Lemma
The Yoneda lemma is one of the most profound results in category theory. In programming terms:
```typescript
import {
toYoneda, fromYoneda, yonedaMap,
cont, contMap, contFlatMap, runCont,
demonstrateFusion,
} from 'category-theory-js';
// Yoneda gives you FREE functor mapping through function composition
// Multiple maps fuse into a single traversal:
const result = demonstrateFusion(
[1, 2, 3],
x => x * 2, // double
x => x + 1, // increment
x => x * x // square
);
// Without fusion: 3 traversals
// With Yoneda: 1 traversal with composed function (x => ((x * 2) + 1)Β²)
console.log(result.isEqual); // true
// Continuations ARE Yoneda (for Identity functor)
// Cont = βR. (A β R) β R
const computation = contFlatMap(
contFlatMap(cont(10), x => cont(x * 2)),
x => cont(x + 5)
);
console.log(runCont(computation)); // 25
```
**Why it matters:**
- **CPS is Yoneda**: Continuation-passing style is the Yoneda embedding
- **Fusion**: Multiple `map` calls become one traversal
- **Coyoneda**: Get a functor instance for ANY type, for free
## Further Reading
- [Category Theory for Programmers](https://bartoszmilewski.com/2014/10/28/category-theory-for-programmers-the-preface/) - Bartosz Milewski
- [fp-ts](https://gcanti.github.io/fp-ts/) - Functional programming in TypeScript
- [Effect](https://effect.website/) - A powerful effect system for TypeScript
- [Professor Frisby's Mostly Adequate Guide](https://mostly-adequate.gitbook.io/mostly-adequate-guide/)
## Related
- [Blog post: Category Theory for JavaScript/TypeScript Developers](https://ibrahimcesar.cloud/blog/category-theory-for-javascript-typescript-developers)
## License
[MIT](./LICENSE)