···11+# Why These Strange Names?
22+33+If you've encountered terms like "Monad" or "Functor" and felt intimidated, you're not alone. This article demystifies all the FP terminology used in purus-ts before you dive into the concepts.
44+55+---
66+77+## Where Do These Names Come From?
88+99+FP terminology comes from several sources:
1010+1111+- **Category theory** - Abstract mathematics from the 1940s (Functor, Monad, Applicative)
1212+- **Type theory** - Formal logic and programming language theory (Option, Refinement)
1313+- **Haskell community** - The language that popularized FP patterns (Either, Maybe)
1414+- **Metaphors** - Descriptive names for patterns (Railway, Fiber, Effect)
1515+1616+The names weren't chosen to be scary - they're precise in their original contexts. Think of them as historical artifacts, like how we still say "dialing" a phone.
1717+1818+**The good news:** You don't need to understand category theory to use these patterns. The concepts are simpler than the names suggest.
1919+2020+---
2121+2222+## Quick Reference: All purus-ts Terms
2323+2424+### Data Types
2525+2626+| Term | Plain English | Also Called |
2727+|------------|-------------------------------------------------------|-----------------------|
2828+| Result | A value that's either success or failure | Either (Haskell) |
2929+| Ok | The success case of Result | Right |
3030+| Err | The failure case of Result | Left |
3131+| Option | A value that might not exist | Maybe (Haskell) |
3232+| Some | The "value exists" case of Option | Just |
3333+| None | The "no value" case of Option | Nothing |
3434+| Validation | Like Result, but collects all errors | Validated |
3535+| Valid | The success case of Validation | - |
3636+| Invalid | The failure case (with error array) | - |
3737+3838+### Effect System
3939+4040+| Term | Plain English | Also Called |
4141+|-------------|------------------------------------------------------|-----------------------|
4242+| Effect/Eff | A description of a computation (not the computation) | IO, Task, ZIO |
4343+| Fiber | A lightweight thread that can be cancelled | Green thread |
4444+| Fork | Start a fiber running in the background | Spawn |
4545+| Join | Wait for a fiber to complete | Await |
4646+| Race | Run two effects, take whichever finishes first | - |
4747+| All | Run effects in parallel, collect all results | Parallel |
4848+| Environment | Dependencies a computation needs (the R in Eff) | Context, Reader |
4949+5050+### Operations
5151+5252+| Term | Plain English | Also Called |
5353+|-------------|------------------------------------------------------|-----------------------|
5454+| map | Transform the value inside a container | fmap |
5555+| flatMap | Chain operations that return containers | bind, chain, >>= |
5656+| fold | Handle both success and failure cases | match, cata |
5757+| pipe | Pass a value through a series of functions | \|> |
5858+| flow | Compose functions left-to-right | >>> |
5959+| traverse | Map + flip container nesting | - |
6060+| sequence | Flip container nesting (traverse with identity) | - |
6161+6262+### Type Class Patterns
6363+6464+| Term | Plain English | Also Called |
6565+|-------------|------------------------------------------------------|-----------------------|
6666+| Functor | Anything you can map over | Mappable |
6767+| Applicative | Combine independent wrapped values | - |
6868+| Monad | Chain dependent wrapped operations | Flatmappable |
6969+| Bifunctor | Map over either of two type parameters | - |
7070+7171+### Type Safety Patterns
7272+7373+| Term | Plain English | Also Called |
7474+|---------------|----------------------------------------------------|-----------------------|
7575+| Branded Type | A primitive with a compile-time "tag" | Newtype, Opaque type |
7676+| Refinement | A type that guarantees a property (e.g., Positive) | Refined type |
7777+| Typestate | Encoding state machine states as types | Phantom types |
7878+7979+---
8080+8181+## Real-World Analogies
8282+8383+### Result = Shipping Package
8484+8585+A package either arrives safely (Ok) or gets damaged/lost (Err). You don't know which until you "open" it, but the tracking system (type system) tells you both are possible.
8686+8787+```typescript
8888+const fetchUser = (id: string): Result<User, ApiError> => ...
8989+// The return type TELLS you: this might fail with ApiError
9090+```
9191+9292+**Why not just throw?** Thrown exceptions don't appear in the type signature. `Result` makes failure visible.
9393+9494+---
9595+9696+### Option = Schrodinger's Box
9797+9898+The box either contains a cat (Some) or is empty (None). You must handle both possibilities - you can't just assume there's a cat.
9999+100100+```typescript
101101+const findUser = (id: string): Option<User> => ...
102102+// Might return a user, might not - the type forces you to handle both
103103+```
104104+105105+**Why not just use `null`?** TypeScript's `null` is easy to forget. `Option` forces you to handle the empty case.
106106+107107+---
108108+109109+### Validation = Report Card
110110+111111+A report card shows ALL your grades, not just the first failing one. If you failed Math, English, and Science, you see all three - not just "Failed Math, try again."
112112+113113+```typescript
114114+// Result: stops at first error
115115+validateUser("", "bad-email", -5) // Err("Name required") - that's all you see
116116+117117+// Validation: shows everything
118118+validateUser("", "bad-email", -5) // Invalid(["Name required", "Invalid email", "Age must be positive"])
119119+```
120120+121121+---
122122+123123+### Effect = Recipe Card
124124+125125+A recipe card isn't food - it's **instructions for making food**. Similarly, an `Eff` isn't a computation - it's **a description of a computation**.
126126+127127+```typescript
128128+const fetchUser: Eff<User, Error, HttpClient> = ...
129129+// This doesn't fetch anything yet - it's just a recipe
130130+// The recipe says: "needs HttpClient, might fail with Error, produces User"
131131+132132+runPromise(fetchUser) // NOW it actually runs
133133+```
134134+135135+**Why not just use Promises?** Promises start immediately. Effects are lazy - you control when they run, and you can compose them before running.
136136+137137+---
138138+139139+### Fiber = Kitchen Assistant
140140+141141+Imagine you're the head chef (main thread). You can ask an assistant (fiber) to chop vegetables while you prepare sauce. You can:
142142+143143+- **Fork**: "Start chopping those onions" (assistant works in parallel)
144144+- **Join**: "Give me the chopped onions" (wait for assistant to finish)
145145+- **Interrupt**: "Stop! We're making pizza instead" (cancel the work)
146146+147147+```typescript
148148+const fiber = yield* fork(longComputation) // Start in background
149149+// ... do other work ...
150150+const result = yield* join(fiber) // Get the result when needed
151151+```
152152+153153+---
154154+155155+### Environment (R) = Kitchen Equipment
156156+157157+A recipe might require specific equipment: an oven, a mixer, a blender. The `R` type parameter lists what "equipment" your effect needs.
158158+159159+```typescript
160160+type Eff<A, E, R>
161161+// ^ ^ ^
162162+// | | └── R = Requirements (what it needs to run)
163163+// | └───── E = Error (what can go wrong)
164164+// └──────── A = Success value (what it produces)
165165+166166+const fetchUser: Eff<User, ApiError, HttpClient> = ...
167167+// ^^^^^^^^^^
168168+// Needs HttpClient to run
169169+```
170170+171171+To run this effect, you must **provide** an HttpClient:
172172+173173+```typescript
174174+pipe(
175175+ fetchUser,
176176+ provide({ fetch: globalThis.fetch }) // Here's your equipment
177177+)
178178+```
179179+180180+---
181181+182182+### pipe = Assembly Line
183183+184184+Products move through stations, each transforming them:
185185+186186+```
187187+Raw Material → [Cut] → [Shape] → [Paint] → [Package] → Finished Product
188188+```
189189+190190+```typescript
191191+pipe(
192192+ rawData, // Raw material
193193+ parse, // Cut
194194+ validate, // Shape
195195+ transform, // Paint
196196+ format // Package
197197+)
198198+```
199199+200200+**Why not just nest function calls?** `format(transform(validate(parse(rawData))))` reads inside-out. `pipe` reads left-to-right, matching how you think about the steps.
201201+202202+---
203203+204204+### flatMap = Choose Your Own Adventure
205205+206206+In a "choose your own adventure" book, each choice leads to a new page with new choices. You can't skip ahead - you must make choice 1 before you see the options for choice 2.
207207+208208+```typescript
209209+pipe(
210210+ chooseDoor(), // "You chose door 2..."
211211+ flatMap(room => exploreRoom(room)), // "In the room, you find..."
212212+ flatMap(item => useItem(item)) // "Using the key, you..."
213213+)
214214+```
215215+216216+Each step depends on the previous result. That's what makes it "flat" - it chains without nesting.
217217+218218+---
219219+220220+### Functor = Gift Wrapping
221221+222222+You can X-ray a wrapped gift and transform what you see without unwrapping it. The wrapping stays intact.
223223+224224+```typescript
225225+pipe(
226226+ ok(42), // Gift box containing 42
227227+ mapResult(n => n.toString()) // X-ray transforms it to "42"
228228+)
229229+// Still wrapped in Ok, now containing "42"
230230+```
231231+232232+---
233233+234234+### Applicative = IKEA Furniture Kit
235235+236236+Separate boxes containing: (1) instructions, (2) panels, (3) screws. Open them in any order, combine at the end. If any box is missing parts, you find out immediately (not after opening box 1, then 2...).
237237+238238+```typescript
239239+pipe(
240240+ valid(makeUser),
241241+ apValidation(validateName(name)), // Box A
242242+ apValidation(validateEmail(email)), // Box B
243243+ apValidation(validateAge(age)) // Box C
244244+)
245245+// All boxes checked; all errors reported at once
246246+```
247247+248248+---
249249+250250+### Monad = Cooking Recipe
251251+252252+Sequential steps where each depends on the previous:
253253+- Can't frost before baking
254254+- Can't bake before mixing
255255+- Can't mix before measuring
256256+257257+```typescript
258258+pipe(
259259+ measureIngredients(),
260260+ flatMap(ingredients => mixBatter(ingredients)),
261261+ flatMap(batter => bake(batter)),
262262+ flatMap(cake => frost(cake))
263263+)
264264+```
265265+266266+---
267267+268268+### Railway Oriented Programming = Train Tracks
269269+270270+Happy path is one track. Errors switch you to a parallel error track. Once on the error track, you stay there.
271271+272272+```
273273+ ┌─────────┐ ┌─────────┐ ┌─────────┐
274274+Success ──▶│ Step 1 │──◆──│ Step 2 │──◆──│ Step 3 │──▶ Ok
275275+ └─────────┘ │ └─────────┘ │ └─────────┘
276276+ │ │
277277+ ▼ ▼
278278+Error ───────────────────────────────────────────────▶ Err
279279+```
280280+281281+```typescript
282282+pipe(
283283+ step1(), // Might fail
284284+ chainResult(step2), // Only runs if step1 succeeded
285285+ chainResult(step3) // Only runs if step2 succeeded
286286+)
287287+// If any step fails, you get that error; later steps don't run
288288+```
289289+290290+---
291291+292292+### Branded Type = Wristband
293293+294294+At a concert, a wristband proves you paid. The wristband is just plastic, but it **brands** you as "paid attendee."
295295+296296+```typescript
297297+type UserId = string & { readonly __brand: "UserId" }
298298+type OrderId = string & { readonly __brand: "OrderId" }
299299+300300+// Both are strings at runtime, but TypeScript won't let you mix them
301301+const processOrder = (orderId: OrderId, userId: UserId) => ...
302302+303303+processOrder(userId, orderId) // ERROR! Wrong order
304304+```
305305+306306+---
307307+308308+### Typestate = Boarding Pass Stages
309309+310310+A boarding pass goes through stages: Booked → CheckedIn → Boarded. You can't board without checking in first - the type system enforces this.
311311+312312+```typescript
313313+type Ticket<Status> = { id: string; _status: Status }
314314+315315+const checkIn = (ticket: Ticket<"Booked">): Ticket<"CheckedIn"> => ...
316316+const board = (ticket: Ticket<"CheckedIn">): Ticket<"Boarded"> => ...
317317+318318+board(bookedTicket) // ERROR! Must check in first
319319+```
320320+321321+---
322322+323323+## The Key Distinctions
324324+325325+### Monad vs Applicative
326326+327327+| Question | Answer | Pattern |
328328+|---------------------------------------|-------------|-------------|
329329+| Does step 2 need step 1's result? | Yes | Monad |
330330+| Are the steps independent? | Yes | Applicative |
331331+| Want to stop on first error? | Yes | Monad |
332332+| Want to collect all errors? | Yes | Applicative |
333333+334334+### Result vs Validation
335335+336336+| Question | Answer | Type |
337337+|---------------------------------------|-------------|-------------|
338338+| Steps depend on each other? | Yes | Result |
339339+| Steps are independent validations? | Yes | Validation |
340340+| Show first error only? | Yes | Result |
341341+| Show all errors? | Yes | Validation |
342342+343343+### Effect vs Promise
344344+345345+| Question | Answer | Type |
346346+|---------------------------------------|-------------|-------------|
347347+| Should it start immediately? | Yes | Promise |
348348+| Should I control when it starts? | Yes | Effect |
349349+| Need cancellation? | Yes | Effect |
350350+| Need typed errors? | Yes | Effect |
351351+| Need dependency injection? | Yes | Effect |
352352+353353+---
354354+355355+## Etymology (For the Curious)
356356+357357+| Term | Origin |
358358+|-------------|---------------------------------------------------------------------------|
359359+| Functor | Latin *functio* ("performance"). Math: mapping between categories. |
360360+| Applicative | You "apply" wrapped functions to wrapped values. |
361361+| Monad | Greek *monas* ("unit"). Leibniz used it for fundamental units of reality. |
362362+| Bifunctor | Latin *bi* ("two") + functor. |
363363+| Option | The value is "optional" - might not exist. |
364364+| Result | The "result" of an operation that might fail. |
365365+| Effect | Describes a side "effect" (IO, state change, etc.). |
366366+| Fiber | Thin threads; lighter than OS threads. |
367367+| Traverse | Latin *traversare* ("to cross over"). Crosses container boundaries. |
368368+369369+---
370370+371371+## The Takeaway
372372+373373+Every scary FP term maps to a simple idea:
374374+375375+| Scary Name | Simple Idea |
376376+|--------------|------------------------------------------------|
377377+| Functor | Transform inside a container |
378378+| Applicative | Combine independent containers |
379379+| Monad | Chain dependent operations |
380380+| Result | Success or failure (not exceptions) |
381381+| Option | Value or nothing (not null) |
382382+| Validation | Collect all errors (not just first) |
383383+| Effect | Recipe for computation (not the computation) |
384384+| Fiber | Cancellable background task |
385385+| pipe | Pass value through functions left-to-right |
386386+| flatMap | Chain operations that return containers |
387387+388388+Once you use these patterns a few times, they become second nature - like "downloading" once felt like jargon but now feels obvious.
389389+390390+---
391391+392392+## What's Next?
393393+394394+Now that you know what the names mean, dive into the details:
395395+396396+- [Why Errors as Values?](./01-errors-as-values.md) - The foundation: Result type
397397+- [Branded Types In Depth](./02-branded-types.md) - Compile-time type safety
398398+- [Validation and Error Accumulation](./03-validation-and-error-accumulation.md) - Applicative in action
399399+- [Type Classes in TypeScript](./04-type-classes-in-typescript.md) - Functor, Applicative, Monad patterns
···11+# Type Classes in TypeScript
22+33+This article explains functional programming type class patterns and how purus implements them within TypeScript's type system limitations.
44+55+---
66+77+## The Problem Type Classes Solve
88+99+Look at these three functions:
1010+1111+```typescript
1212+// Transform values inside a Result
1313+const mapResult = <T, U, E>(f: (t: T) => U) =>
1414+ (result: Result<T, E>): Result<U, E> =>
1515+ result._tag === "Ok" ? ok(f(result.value)) : result
1616+1717+// Transform values inside an Option
1818+const mapOption = <T, U>(f: (t: T) => U) =>
1919+ (option: Option<T>): Option<U> =>
2020+ option._tag === "Some" ? some(f(option.value)) : option
2121+2222+// Transform values inside an Array
2323+const mapArray = <T, U>(f: (t: T) => U) =>
2424+ (arr: T[]): U[] =>
2525+ arr.map(f)
2626+```
2727+2828+All three do the same thing: apply a function to values **inside** a container.
2929+3030+In Haskell, you'd write one generic `map`:
3131+3232+```haskell
3333+class Functor f where
3434+ fmap :: (a -> b) -> f a -> f b
3535+```
3636+3737+Then Result, Option, and Array would all be instances of Functor, sharing the same `fmap` interface.
3838+3939+### Why TypeScript Can't Do This
4040+4141+TypeScript lacks **higher-kinded types** (HKT). You can't write:
4242+4343+```typescript
4444+// This is NOT valid TypeScript
4545+interface Functor<F> {
4646+ map: <A, B>(f: (a: A) => B) => (fa: F<A>) => F<B>
4747+}
4848+```
4949+5050+The problem is `F<A>`. TypeScript doesn't support type constructors as type parameters. You can pass `number` as a type, but you can't pass `Result` (without its type arguments) as a type.
5151+5252+### What purus Does Instead
5353+5454+purus provides type class functions for each concrete type:
5555+5656+- `mapResult` for Result
5757+- `mapOption` for Option
5858+- `mapEff` for Eff
5959+6060+The patterns are consistent, even if we can't abstract over them perfectly.
6161+6262+---
6363+6464+## What Are Higher-Kinded Types?
6565+6666+A quick explanation for the curious.
6767+6868+### Types vs Type Constructors
6969+7070+- `number` is a **type** - it's complete, you can have values of type `number`
7171+- `Result` is a **type constructor** - it needs arguments to become a type
7272+- `Result<number, string>` is a **type** - now it's complete
7373+7474+Think of it like functions:
7575+7676+- A value like `42` is complete
7777+- A function like `(x) => x * 2` needs an argument to produce a value
7878+- `((x) => x * 2)(21)` produces `42`
7979+8080+Type constructors are like functions at the type level.
8181+8282+### The Limitation
8383+8484+TypeScript lets you pass types as parameters:
8585+8686+```typescript
8787+const identity = <T>(x: T): T => x
8888+identity<number>(42) // T = number
8989+```
9090+9191+But you can't pass type constructors:
9292+9393+```typescript
9494+// Hypothetical - NOT valid TypeScript
9595+const mapGeneric = <F, A, B>(f: (a: A) => B, fa: F<A>): F<B> => ...
9696+mapGeneric<Result, number, string>(...) // Can't pass Result like this
9797+```
9898+9999+### Workarounds Exist But Are Complex
100100+101101+Libraries like fp-ts use encoding tricks (TypeScript's module augmentation) to simulate HKT. purus opts for simplicity: explicit functions per type, consistent patterns.
102102+103103+> **New to FP terminology?** Read [Why These Strange Names?](./00-why-these-strange-names.md) first for plain-English explanations and real-world analogies.
104104+105105+---
106106+107107+## The Type Class Hierarchy
108108+109109+Type classes form a hierarchy where each level adds capabilities:
110110+111111+```
112112+ ┌──────────┐
113113+ │ Functor │
114114+ │ map() │
115115+ └────┬─────┘
116116+ │
117117+ ▼
118118+ ┌─────────────┐
119119+ │ Applicative │
120120+ │ of(), ap() │
121121+ └──────┬──────┘
122122+ │
123123+ ▼
124124+ ┌─────────┐
125125+ │ Monad │
126126+ │flatMap()│
127127+ └─────────┘
128128+129129+130130+ ┌────────────┐
131131+ │ Bifunctor │ (separate hierarchy)
132132+ │ bimap() │
133133+ └────────────┘
134134+```
135135+136136+Every Monad is also an Applicative, and every Applicative is also a Functor.
137137+138138+---
139139+140140+## Functor: Mapping in Context
141141+142142+A Functor lets you transform values inside a container without changing the container's structure.
143143+144144+### The Operation
145145+146146+```typescript
147147+map: <A, B>(f: (a: A) => B) => (fa: F<A>) => F<B>
148148+```
149149+150150+Apply function `f` to the value inside `F`, producing a new `F` with the transformed value.
151151+152152+### Examples in purus
153153+154154+```typescript
155155+import { ok, err, mapResult, pipe } from "purus-ts"
156156+157157+// Result as Functor
158158+pipe(
159159+ ok(10),
160160+ mapResult(n => n * 2)
161161+) // Ok(20)
162162+163163+pipe(
164164+ err("oops"),
165165+ mapResult(n => n * 2)
166166+) // Err("oops") - structure unchanged
167167+```
168168+169169+```typescript
170170+import { some, none, mapOption, pipe } from "purus-ts"
171171+172172+// Option as Functor
173173+pipe(
174174+ some(10),
175175+ mapOption(n => n * 2)
176176+) // Some(20)
177177+178178+pipe(
179179+ none,
180180+ mapOption(n => n * 2)
181181+) // None - structure unchanged
182182+```
183183+184184+### The Laws
185185+186186+Functors must obey two laws:
187187+188188+**Identity:** Mapping the identity function does nothing
189189+190190+```typescript
191191+pipe(ok(42), mapResult(x => x))
192192+// Must equal: ok(42)
193193+```
194194+195195+**Composition:** Mapping f then g equals mapping their composition
196196+197197+```typescript
198198+const f = (x: number) => x * 2
199199+const g = (x: number) => x + 1
200200+201201+// These must be equal:
202202+pipe(ok(10), mapResult(f), mapResult(g))
203203+pipe(ok(10), mapResult(x => g(f(x))))
204204+// Both: Ok(21)
205205+```
206206+207207+### Intuition
208208+209209+Think of `map` as reaching inside the container:
210210+211211+- **Array**: transform each element
212212+- **Option**: transform the value if present
213213+- **Result**: transform the success value
214214+- **Promise**: transform the resolved value (via `.then`)
215215+216216+---
217217+218218+## Applicative: Independent Composition
219219+220220+Applicative extends Functor with two operations:
221221+222222+- `of` (also called `pure`): wrap a value in the context
223223+- `ap`: apply a wrapped function to a wrapped value
224224+225225+### The Operations
226226+227227+```typescript
228228+of: <A>(a: A) => F<A>
229229+ap: <A, B>(fa: F<A>) => (fab: F<(a: A) => B>) => F<B>
230230+```
231231+232232+### Why Applicative Matters
233233+234234+Applicative enables combining **independent** computations.
235235+236236+```
237237+Monad (sequential, dependent):
238238+ ┌───┐ ┌───┐ ┌───┐
239239+ │ A │───▶│ B │───▶│ C │ B needs A's result
240240+ └───┘ └─┬─┘ └───┘ C needs B's result
241241+ │
242242+ depends
243243+244244+Applicative (parallel, independent):
245245+ ┌───┐
246246+ │ A │──┐
247247+ └───┘ │
248248+ ┌───┐ ├──▶ combine
249249+ │ B │──┤
250250+ └───┘ │
251251+ ┌───┐ │
252252+ │ C │──┘
253253+ └───┘
254254+```
255255+256256+With Monad, each step depends on the previous - you can't compute B until A completes.
257257+258258+With Applicative, A, B, and C are independent - they can run in parallel (or in Validation's case, all errors can be collected).
259259+260260+### apResult in purus
261261+262262+```typescript
263263+import { ok, err, apResult, pipe } from "purus-ts"
264264+265265+// Apply a function in Result to a value in Result
266266+pipe(
267267+ ok((x: number) => x * 2),
268268+ apResult(ok(10))
269269+) // Ok(20)
270270+271271+// Short-circuits on first error (Result is monadic)
272272+pipe(
273273+ ok((x: number) => x * 2),
274274+ apResult(err("no value"))
275275+) // Err("no value")
276276+```
277277+278278+### apValidation - Applicative with Error Accumulation
279279+280280+```typescript
281281+import { valid, invalid, apValidation, pipe } from "purus-ts"
282282+283283+// Both valid - applies function
284284+pipe(
285285+ valid((x: number) => x * 2),
286286+ apValidation(valid(10))
287287+) // Valid(20)
288288+289289+// Both invalid - accumulates errors!
290290+pipe(
291291+ invalid(["error 1"]),
292292+ apValidation(invalid(["error 2"]))
293293+) // Invalid(["error 1", "error 2"])
294294+```
295295+296296+This is the key difference: `apResult` short-circuits, `apValidation` accumulates.
297297+298298+### liftA2 - Lifting Binary Functions
299299+300300+A common pattern is lifting a binary function to work with wrapped values:
301301+302302+```typescript
303303+import { liftA2Result, ok, err } from "purus-ts"
304304+305305+const add = (a: number, b: number) => a + b
306306+const addResults = liftA2Result(add)
307307+308308+addResults(ok(1), ok(2)) // Ok(3)
309309+addResults(ok(1), err("!")) // Err("!")
310310+addResults(err("!"), ok(2)) // Err("!")
311311+```
312312+313313+This is equivalent to:
314314+315315+```typescript
316316+pipe(
317317+ ok((a: number) => (b: number) => a + b),
318318+ apResult(ok(1)),
319319+ apResult(ok(2))
320320+)
321321+```
322322+323323+---
324324+325325+## Monad: Sequential Composition
326326+327327+Monad extends Applicative with `flatMap` (also called `bind` or `chain`):
328328+329329+```typescript
330330+flatMap: <A, B>(f: (a: A) => F<B>) => (fa: F<A>) => F<B>
331331+```
332332+333333+The function `f` returns a new wrapped value, and `flatMap` "flattens" the result (avoiding `F<F<B>>`).
334334+335335+### Why Monad Matters
336336+337337+Monad enables **dependent** sequencing - each step can use the result of the previous step.
338338+339339+```typescript
340340+import { ok, err, chainResult, pipe } from "purus-ts"
341341+342342+const fetchUser = (id: string): Result<User, Error> => ...
343343+const fetchOrders = (userId: string): Result<Order[], Error> => ...
344344+345345+// fetchOrders depends on fetchUser's result
346346+pipe(
347347+ fetchUser("123"),
348348+ chainResult(user => fetchOrders(user.id)) // Uses user.id
349349+)
350350+```
351351+352352+### The Three Monad Laws
353353+354354+**Left Identity:** wrapping a value then flatMapping equals calling the function directly
355355+356356+```typescript
357357+const f = (n: number): Result<string, never> => ok(n.toString())
358358+359359+// These must be equal:
360360+pipe(ok(42), chainResult(f))
361361+f(42)
362362+// Both: Ok("42")
363363+```
364364+365365+**Right Identity:** flatMapping with the wrapper function does nothing
366366+367367+```typescript
368368+// These must be equal:
369369+pipe(ok(42), chainResult(ok))
370370+ok(42)
371371+```
372372+373373+**Associativity:** order of composition doesn't matter
374374+375375+```typescript
376376+const f = (n: number): Result<number, string> => ok(n * 2)
377377+const g = (n: number): Result<number, string> => ok(n + 1)
378378+379379+// These must be equal:
380380+pipe(ok(10), chainResult(f), chainResult(g))
381381+pipe(ok(10), chainResult(x => pipe(f(x), chainResult(g))))
382382+// Both: Ok(21)
383383+```
384384+385385+### Result and Option as Monads
386386+387387+```typescript
388388+// Result Monad
389389+pipe(
390390+ ok(10),
391391+ chainResult(n => n > 0 ? ok(n * 2) : err("not positive")),
392392+ chainResult(n => ok(n.toString()))
393393+) // Ok("20")
394394+395395+// Option Monad
396396+pipe(
397397+ some(10),
398398+ flatMapOption(n => n > 0 ? some(n * 2) : none),
399399+ flatMapOption(n => some(n.toString()))
400400+) // Some("20")
401401+```
402402+403403+### Monad vs Applicative: When to Use Each
404404+405405+| Pattern | Use When | Example |
406406+|--------------|---------------------------------------------|----------------------------------|
407407+| Monad | Steps depend on previous results | Fetch user, then fetch their orders |
408408+| Applicative | Steps are independent | Validate name AND email AND age |
409409+410410+---
411411+412412+## Bifunctor: Mapping Both Sides
413413+414414+Some types have two type parameters that can both be mapped. Result is the canonical example: you might want to transform the success value OR the error value.
415415+416416+### The Operation
417417+418418+```typescript
419419+bimap: <A, B, C, D>(
420420+ first: (a: A) => B,
421421+ second: (c: C) => D
422422+) => (fa: F<A, C>) => F<B, D>
423423+```
424424+425425+For Result, this is:
426426+427427+```typescript
428428+bimap: <T, U, E, F>(
429429+ onOk: (t: T) => U,
430430+ onErr: (e: E) => F
431431+) => (result: Result<T, E>) => Result<U, F>
432432+```
433433+434434+### bimap in purus
435435+436436+```typescript
437437+import { ok, err, bimap, pipe } from "purus-ts"
438438+439439+// Transform the Ok value
440440+pipe(
441441+ ok(10),
442442+ bimap(
443443+ n => n * 2,
444444+ e => e.toUpperCase()
445445+ )
446446+) // Ok(20)
447447+448448+// Transform the Err value
449449+pipe(
450450+ err("oops"),
451451+ bimap(
452452+ n => n * 2,
453453+ e => e.toUpperCase()
454454+ )
455455+) // Err("OOPS")
456456+```
457457+458458+### Practical Use: Error Type Conversion
459459+460460+`bimap` is useful at module boundaries where error types differ:
461461+462462+```typescript
463463+type DbError = { _tag: "DbError"; message: string }
464464+type ApiError = { _tag: "ApiError"; code: number; message: string }
465465+466466+const dbResultToApiResult = <T>(result: Result<T, DbError>): Result<T, ApiError> =>
467467+ pipe(
468468+ result,
469469+ bimap(
470470+ value => value, // Pass through success unchanged
471471+ dbErr => ({ _tag: "ApiError", code: 500, message: dbErr.message })
472472+ )
473473+ )
474474+```
475475+476476+Or more commonly, just use `mapErr`:
477477+478478+```typescript
479479+pipe(
480480+ dbResult,
481481+ mapErr(dbErr => ({ _tag: "ApiError", code: 500, message: dbErr.message }))
482482+)
483483+```
484484+485485+### Bifunctor Laws
486486+487487+**Identity:** mapping both sides with identity does nothing
488488+489489+```typescript
490490+pipe(ok(42), bimap(x => x, e => e))
491491+// Must equal: ok(42)
492492+```
493493+494494+**Composition:** composing bimaps equals bimap of compositions
495495+496496+```typescript
497497+const f1 = (n: number) => n * 2
498498+const f2 = (n: number) => n + 1
499499+const g1 = (s: string) => s.toUpperCase()
500500+const g2 = (s: string) => s + "!"
501501+502502+// These must be equal:
503503+pipe(ok(10), bimap(f1, g1), bimap(f2, g2))
504504+pipe(ok(10), bimap(x => f2(f1(x)), e => g2(g1(e))))
505505+```
506506+507507+---
508508+509509+## Traverse and Sequence
510510+511511+Traverse and sequence flip the nesting of two type constructors.
512512+513513+### The Intuition
514514+515515+You have an array of things that produce effects:
516516+517517+```typescript
518518+const userIds: string[] = ["1", "2", "3"]
519519+const fetchUser = (id: string): Eff<User, Error, HttpClient> => ...
520520+```
521521+522522+If you map `fetchUser` over the array, you get:
523523+524524+```typescript
525525+const effects: Eff<User, Error, HttpClient>[] = userIds.map(fetchUser)
526526+// Array of effects - not what we want
527527+```
528528+529529+But you want:
530530+531531+```typescript
532532+const effect: Eff<User[], Error, HttpClient> = ???
533533+// One effect that produces an array
534534+```
535535+536536+That transformation - flipping `Array<Eff<A>>` to `Eff<Array<A>>` - is what traverse and sequence do.
537537+538538+```
539539+traverse(fetchUser):
540540+541541+ ["1", "2", "3"] Eff<[User, User, User]>
542542+ ┌───┬───┬───┐ ┌─────────────────────┐
543543+ │"1"│"2"│"3"│ ───── traverse ────▶│ [User1, User2, User3]│
544544+ └───┴───┴───┘ (fetchUser) └─────────────────────┘
545545+ Array<String> Eff<Array<User>>
546546+547547+ "Flips" the nesting: [F<A>] → F<[A]>
548548+```
549549+550550+### traverse in purus
551551+552552+```typescript
553553+import { traverse, pipe } from "purus-ts"
554554+555555+const fetchUser = (id: string): Eff<User, Error, HttpClient> => ...
556556+557557+// Apply fetchUser to each id, collect results
558558+pipe(
559559+ ["1", "2", "3"],
560560+ traverse(fetchUser)
561561+) // Eff<readonly User[], Error, HttpClient>
562562+```
563563+564564+Runs sequentially, short-circuits on first error.
565565+566566+### sequence in purus
567567+568568+When you already have an array of effects:
569569+570570+```typescript
571571+import { sequence } from "purus-ts"
572572+573573+const effects: Eff<User, Error, HttpClient>[] = [
574574+ fetchUser("1"),
575575+ fetchUser("2"),
576576+ fetchUser("3")
577577+]
578578+579579+const combined = sequence(effects)
580580+// Eff<readonly User[], Error, HttpClient>
581581+```
582582+583583+`sequence` is just `traverse` with the identity function.
584584+585585+### Parallel Variants
586586+587587+For independent effects that can run concurrently:
588588+589589+```typescript
590590+import { traversePar, sequencePar } from "purus-ts"
591591+592592+// Run all fetches in parallel
593593+pipe(
594594+ ["1", "2", "3"],
595595+ traversePar(fetchUser)
596596+) // All run concurrently, fail-fast on first error
597597+598598+// Parallel sequence
599599+const combined = sequencePar(effects)
600600+```
601601+602602+### When to Use Each
603603+604604+| Function | Use When |
605605+|---------------|---------------------------------------------|
606606+| traverse | Transform + collect, sequential |
607607+| sequence | Already have effects, just combine |
608608+| traversePar | Independent operations, want concurrency |
609609+| sequencePar | Already have effects, run in parallel |
610610+611611+---
612612+613613+## The Educational Value
614614+615615+You might wonder: if TypeScript can't fully express type classes, why learn them?
616616+617617+### Recognizing Patterns
618618+619619+Once you know the Functor/Applicative/Monad pattern, you recognize it everywhere:
620620+621621+- Promise is a Monad (`.then` is `flatMap`)
622622+- Array is a Monad (`.flatMap` is `flatMap`)
623623+- RxJS Observable is a Monad
624624+- React's `useState` hook follows monadic patterns
625625+626626+### Making Design Decisions
627627+628628+Understanding that Applicative = independent and Monad = dependent helps you choose:
629629+630630+- Form validation? Applicative (Validation) - collect all errors
631631+- Database transactions? Monad (Result/Eff) - each step depends on previous
632632+633633+### Communicating with FP Developers
634634+635635+If someone says "just use the Applicative instance", you know they mean use `ap` to combine independent computations without short-circuiting.
636636+637637+### Understanding Library Documentation
638638+639639+Many FP libraries (fp-ts, Effect, etc.) use type class terminology. Knowing these concepts makes their documentation accessible.
640640+641641+---
642642+643643+## Type Class Instances in purus
644644+645645+purus exports instance objects showing which type classes each type satisfies:
646646+647647+```typescript
648648+import { resultInstances, optionInstances } from "purus-ts"
649649+650650+// Result satisfies Functor, Applicative, Monad, and Bifunctor
651651+resultInstances.map // mapResult
652652+resultInstances.of // ok
653653+resultInstances.ap // apResult
654654+resultInstances.flatMap // chainResult
655655+resultInstances.bimap // bimap
656656+657657+// Option satisfies Functor, Applicative, and Monad
658658+optionInstances.map // mapOption
659659+optionInstances.of // some
660660+optionInstances.ap // apOption
661661+optionInstances.flatMap // flatMapOption
662662+```
663663+664664+These are for educational purposes - in practice, you'll use the named functions directly.
665665+666666+---
667667+668668+## Key Takeaways
669669+670670+1. **Type classes are interfaces for types** - Functor, Applicative, Monad define operations types must support
671671+2. **TypeScript lacks HKT** - We can't write truly generic type class code, but patterns remain consistent
672672+3. **Functor = map** - Transform values inside a container
673673+4. **Applicative = independent composition** - Combine operations that don't depend on each other
674674+5. **Monad = sequential composition** - Chain operations where each step depends on the previous
675675+6. **Bifunctor = map both sides** - Transform either the success or error value
676676+7. **Traverse/Sequence = flip nesting** - Turn `[F<A>]` into `F<[A]>`
677677+8. **The patterns transfer** - Learn these once, recognize them in any FP library
678678+679679+Understanding type classes gives you a vocabulary for describing common patterns and helps you choose the right abstraction for each problem.
680680+681681+---
682682+683683+## See Also
684684+685685+- [Validation and Error Accumulation](./03-validation-and-error-accumulation.md) - Applicative in action
686686+- [Why Errors as Values?](./01-errors-as-values.md) - Result fundamentals
687687+- [Effect Composition](./effect-composition.md) - How effects compose internally
688688+- [Tutorial Chapter 3: Typed Errors with Result](../tutorial/03-typed-errors-with-result.md) - Hands-on Result practice
+49-3
docs/guides/concepts/README.md
···2233Deep-dives into specific topics when you need more detail.
4455-Each article is self-contained - read them in any order based on what you need.
55+Articles are numbered in recommended reading order, but each is self-contained.
6677---
8899## Articles
10101111-### [Why Errors as Values?](./errors-as-values.md)
1111+### [00 - Why These Strange Names?](./00-why-these-strange-names.md)
1212+1313+Demystifying Functor, Monad, and other FP terminology.
1414+1515+**Topics Covered:**
1616+- Where these names come from (category theory)
1717+- Plain English translations
1818+- Real-world analogies (Gift Box, IKEA Kit, Recipe)
1919+- When to use Monad vs Applicative
2020+2121+**Best For:** First read before diving into type classes. Reduces intimidation.
2222+2323+---
2424+2525+### [01 - Why Errors as Values?](./01-errors-as-values.md)
12261327The fundamental shift from exceptions to typed errors.
1428···23372438---
25392626-### [Branded Types In Depth](./branded-types.md)
4040+### [02 - Branded Types In Depth](./02-branded-types.md)
27412842Creating distinct types from primitives for compile-time safety.
2943···3549- Type-level testing techniques
36503751**Best For:** Preventing "wrong argument order" bugs, domain modeling.
5252+5353+---
5454+5555+### [03 - Validation and Error Accumulation](./03-validation-and-error-accumulation.md)
5656+5757+Collecting all validation errors instead of stopping at the first.
5858+5959+**Topics Covered:**
6060+- The short-circuit problem with Result
6161+- Monadic vs Applicative composition
6262+- The Validation type and apValidation
6363+- Converting between Result and Validation
6464+- Real-world form validation example
6565+6666+**Best For:** Form validation, config parsing, any multi-field validation.
6767+6868+---
6969+7070+### [04 - Type Classes in TypeScript](./04-type-classes-in-typescript.md)
7171+7272+Understanding Functor, Applicative, Monad, and Bifunctor patterns.
7373+7474+**Topics Covered:**
7575+- What type classes solve
7676+- Why TypeScript can't fully express them (HKT)
7777+- Functor, Applicative, Monad, Bifunctor
7878+- traverse and sequence
7979+- When to use each pattern
8080+8181+**Best For:** Understanding FP abstractions, choosing between patterns.
38823983---
4084···119163|---------------|--------------------------------------|
120164| Result | Errors as values, not exceptions |
121165| Option | Nullable values without null |
166166+| Validation | Accumulate all errors, not just first|
122167| Branded Types | Distinct types from primitives |
123168| Typestate | State machines in the type system |
124169| Effect | Lazy, composable, typed async |
125170| Fiber | Lightweight thread with cancellation |
126171| Environment | Dependencies as a type parameter |
172172+| Type Classes | Functor, Applicative, Monad patterns |
127173128174---
129175