An educational pure functional programming library in TypeScript
2
fork

Configure Feed

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

Replace type casting with type guards and helper functions

+37 -12
+2
docs/guides/concepts/branded-types.md
··· 89 89 90 90 The compiler catches the bug before the code runs. 91 91 92 + > **Note on casting:** The examples above use `as` for clarity, but in application code, prefer the `brand()` helper function which encapsulates the cast. See the [Smart Constructors](#smart-constructors) section below. 93 + 92 94 ### How It Works 93 95 94 96 The `Brand<B>` type adds a phantom property that only exists at the type level:
+10 -3
docs/guides/tutorial/07-the-effect-system.md
··· 270 270 | { _tag: "NotFound"; url: string } 271 271 | { _tag: "ServerError"; status: number } 272 272 273 + // Type guard for ApiError 274 + const isApiError = (e: unknown): e is ApiError => 275 + e !== null && 276 + typeof e === "object" && 277 + "_tag" in e && 278 + (e._tag === "NetworkError" || e._tag === "NotFound" || e._tag === "ServerError") 279 + 273 280 const fetchJson = <T>(url: string): Eff<T, ApiError, unknown> => 274 281 pipe( 275 282 fromPromise(() => fetch(url)), ··· 281 288 : fromPromise(() => response.json()) 282 289 ), 283 290 catchAll(e => 284 - e && typeof e === "object" && "_tag" in e 285 - ? fail(e as ApiError) 291 + isApiError(e) 292 + ? fail(e) 286 293 : fail({ _tag: "NetworkError", message: String(e) }) 287 294 ) 288 - ) as Eff<T, ApiError, unknown> 295 + ) 289 296 290 297 // Usage 291 298 type User = { id: number; name: string }
+4 -1
docs/guides/tutorial/10-building-a-complete-app.md
··· 177 177 ) 178 178 179 179 // Mark task as done 180 + // Helper to create Done status (avoids type assertion) 181 + const doneStatus = (completedAt: Date): TaskStatus => ({ _tag: "Done", completedAt }) 182 + 180 183 const completeTask = (id: TaskId): Eff<Task, AppError, AppEnv> => 181 184 pipe( 182 185 loadTasks(), ··· 185 188 (task: Task) => { 186 189 const updated = updateTask(tasks, id, t => ({ 187 190 ...t, 188 - status: { _tag: "Done", completedAt: new Date() } as TaskStatus, 191 + status: doneStatus(new Date()), 189 192 })) 190 193 return pipe( 191 194 saveTasks(updated),
+21 -8
examples/http-client/with-purus.ts
··· 36 36 pipe, 37 37 match, 38 38 runPromise, 39 + runPromiseExit, 39 40 } from "../../src/index" 40 41 41 42 // ============================================================================= ··· 73 74 email: string 74 75 } 75 76 77 + // Type guard - validates the shape at runtime without casting 78 + const isUser = (data: unknown): data is User => 79 + typeof data === "object" && 80 + data !== null && 81 + "id" in data && 82 + "name" in data && 83 + "email" in data 84 + 76 85 // ============================================================================= 77 86 // SECTION 2: Fetch as an Effect 78 87 // ============================================================================= ··· 101 110 fetch(url, { signal: controller.signal }) 102 111 .then(async (response) => { 103 112 if (response.ok) { 104 - const data = await response.json() 105 - resume(Exit.succeed(data as User)) 113 + const data: unknown = await response.json() 114 + // Use type guard instead of casting - validates at runtime 115 + isUser(data) 116 + ? resume(Exit.succeed(data)) 117 + : resume(Exit.fail(HttpError.serverError(500))) 106 118 } else if (response.status === 404) { 107 119 resume(Exit.fail(HttpError.notFound(url))) 108 120 } else { ··· 181 193 ) 182 194 ) 183 195 184 - try { 185 - const user = await runPromise(request1) 186 - console.log(`Got user: ${user.name} (${user.email})\n`) 187 - } catch (e) { 188 - console.log(`Error: ${handleError(e as HttpError)}\n`) 189 - } 196 + // Use runPromiseExit instead of try/catch - no type casting needed 197 + const exit1 = await runPromiseExit(request1) 198 + exit1._tag === "Success" 199 + ? console.log(`Got user: ${exit1.value.name} (${exit1.value.email})\n`) 200 + : exit1._tag === "Failure" 201 + ? console.log(`Error: ${handleError(exit1.error)}\n`) 202 + : console.log("Interrupted\n") 190 203 191 204 // --------------------------------------------------------------------------- 192 205 // Test 2: Short timeout - will fail, but we recover