An educational pure functional programming library in TypeScript
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add educational JSDoc comments to escape hatch functions

+68 -11
+28
src/effect/eff.ts
··· 42 42 export const sync = <A>(f: () => A): Eff<A, never, unknown> => 43 43 ({ _tag: "Sync", f }) 44 44 45 + /** 46 + * ESCAPE HATCH: Bridges callback-based async APIs into the effect system. 47 + * 48 + * JavaScript's world is full of callback-based APIs (setTimeout, event listeners, 49 + * Node.js callbacks). This function lets you wrap them into pure Eff values that 50 + * can be composed, sequenced, raced, and cancelled like any other effect. 51 + * 52 + * The register function receives a `resume` callback. Call it with an Exit when 53 + * the async operation completes. Return a cleanup function for cancellation. 54 + * 55 + * Use this to integrate external async APIs. For Promises, prefer `fromPromise`. 56 + * 57 + * @example 58 + * // Wrap setTimeout 59 + * const delay = (ms: number): Eff<void, never, unknown> => 60 + * async(resume => { 61 + * const id = setTimeout(() => resume({ _tag: "Success", value: undefined }), ms) 62 + * return () => clearTimeout(id) // cleanup on cancellation 63 + * }) 64 + * 65 + * // Wrap event listener 66 + * const onClick = (el: HTMLElement): Eff<MouseEvent, never, unknown> => 67 + * async(resume => { 68 + * const handler = (e: MouseEvent) => resume({ _tag: "Success", value: e }) 69 + * el.addEventListener("click", handler) 70 + * return () => el.removeEventListener("click", handler) 71 + * }) 72 + */ 45 73 export const async = <A, E>( 46 74 register: (resume: (exit: Exit<A, E>) => void) => Cleanup 47 75 ): Eff<A, E, unknown> =>
+20 -8
src/effect/trampoline.ts
··· 9 9 import { interpret } from "./interpret" 10 10 11 11 /** 12 - * ESCAPE HATCH: Trampoline - the boundary between pure interpretation and JS execution. 12 + * ESCAPE HATCH: The boundary between pure effect interpretation and JS execution. 13 13 * 14 - * This is one of three documented escape hatches in purus. The trampoline: 15 - * 1. Takes pure Step values from the interpreter 16 - * 2. Executes them using JavaScript's runtime (Promises, event loop) 17 - * 3. Returns a Promise with the final Exit 14 + * Eff values are pure data - they describe WHAT to do, not HOW. The trampoline 15 + * is where we actually DO it, using JavaScript's runtime (Promises, event loop). 18 16 * 19 - * EDUCATIONAL NOTE: In a pure language, this would be handled by the runtime. 20 - * In JS, we must have this impure boundary somewhere. By isolating it here, 21 - * all other code remains pure and testable. 17 + * This is the heart of "effects as data": the interpreter (interpret.ts) produces 18 + * pure Step values, and the trampoline executes them. By isolating all impurity 19 + * here, the rest of purus remains pure, composable, and testable. 20 + * 21 + * In a language like Haskell, the runtime handles this. In JS, we must build it. 22 + * 23 + * The trampoline handles three cases: 24 + * - Done: Return the final Exit value 25 + * - Suspended: Yield to event loop (prevents stack overflow), then continue 26 + * - Blocked: Wait for async operation, then continue 27 + * 28 + * @example 29 + * // You rarely call this directly - use runPromise, runFiber, etc. 30 + * // But understanding it helps you understand the effect model: 31 + * const step = interpret(myEffect, ctx, makeFiber) 32 + * const exit = await trampoline(step) 33 + * // exit: Exit<A, E> - Success, Failure, or Interrupted 22 34 */ 23 35 export const trampoline = <A, E>(step: Step<A, E>): Promise<Exit<A, E>> => 24 36 match(step)({
+20 -3
src/prelude/result.ts
··· 37 37 result._tag === "Ok" ? onOk(result.value) : onErr(result.error) 38 38 39 39 /** 40 - * ESCAPE HATCH: Catches JavaScript exceptions at the boundary. 41 - * JS uses exceptions for errors, but FP uses Result. This bridges that gap. 42 - * Inside purus, we use Result - this is only for external JS interop. 40 + * ESCAPE HATCH: Bridges thrown exceptions to Result values. 41 + * 42 + * JavaScript throws exceptions for errors, but pure FP treats errors as values. 43 + * This function catches any thrown exception and converts it to Err, letting 44 + * you safely call impure JS code from your pure functional core. 45 + * 46 + * Use this at the boundary between impure JS libraries and your pure code. 47 + * Inside purus, all error handling uses Result - no exceptions thrown. 48 + * 49 + * @example 50 + * // Safely parse untrusted JSON 51 + * const result = tryCatch(() => JSON.parse(userInput)) 52 + * // Result<unknown, unknown> - exception becomes Err, no throws! 53 + * 54 + * // Chain with Result operations 55 + * pipe( 56 + * tryCatch(() => riskyOperation()), 57 + * mapResult(transform), 58 + * unwrapOr(defaultValue) 59 + * ) 43 60 */ 44 61 export const tryCatch = <T>(f: () => T): Result<T, unknown> => { 45 62 try {