functype 0.40.0 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/Brand-BPeggBaO.d.ts +1 -2
  2. package/dist/Brand-Cfr5zy8F.js +1 -2
  3. package/dist/Tuple-C4maYbiO.d.ts +1 -2
  4. package/dist/Tuple-CgX4p79w.js +1 -2
  5. package/dist/cli/index.js +2 -3
  6. package/dist/do/index.d.ts +1 -1
  7. package/dist/do/index.js +1 -1
  8. package/dist/either/index.d.ts +1 -1
  9. package/dist/either/index.js +1 -1
  10. package/dist/fpromise/index.d.ts +1 -1
  11. package/dist/fpromise/index.js +1 -1
  12. package/dist/{index-Bn_yRBx8.d.ts → index-B6Civ4kr.d.ts} +19 -9
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/list/index.d.ts +1 -1
  16. package/dist/list/index.js +1 -1
  17. package/dist/map/index.d.ts +1 -1
  18. package/dist/map/index.js +1 -1
  19. package/dist/option/index.d.ts +1 -1
  20. package/dist/option/index.js +1 -1
  21. package/dist/set/index.d.ts +1 -1
  22. package/dist/set/index.js +1 -1
  23. package/dist/{src-JcsaR9MX.js → src-DpfaJv6K.js} +2 -3
  24. package/dist/try/index.d.ts +1 -1
  25. package/dist/try/index.js +1 -1
  26. package/package.json +35 -38
  27. package/README.processed.md +0 -862
  28. package/dist/Brand-Cfr5zy8F.js.map +0 -1
  29. package/dist/Tuple-CgX4p79w.js.map +0 -1
  30. package/dist/cli/index.js.map +0 -1
  31. package/dist/src-JcsaR9MX.js.map +0 -1
  32. package/readme/BRAND_MIGRATION_GUIDE.md +0 -230
  33. package/readme/BUNDLE_OPTIMIZATION.md +0 -74
  34. package/readme/FPromise-Assessment.md +0 -43
  35. package/readme/HKT.md +0 -110
  36. package/readme/ROADMAP.md +0 -113
  37. package/readme/TASK-TODO.md +0 -33
  38. package/readme/TUPLE-EXAMPLES.md +0 -76
  39. package/readme/TaskMigration.md +0 -129
  40. package/readme/ai-guide.md +0 -406
  41. package/readme/examples.md +0 -2093
  42. package/readme/functype-changes-required.md +0 -189
  43. package/readme/quick-reference.md +0 -514
  44. package/readme/task-error-handling.md +0 -283
  45. package/readme/tasks.md +0 -203
  46. package/readme/type-index.md +0 -238
@@ -1,862 +0,0 @@
1
- # Functype
2
-
3
- ![NPM Version](https://img.shields.io/npm/v/functype?link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Ffunctype)
4
- [![Node.js Build](https://github.com/jordanburke/functype/actions/workflows/pnpm-build.yml/badge.svg)](https://github.com/jordanburke/functype/actions/workflows/pnpm-build.yml)
5
-
6
- ## A Functional Programming Library for TypeScript
7
-
8
- Functype is a lightweight functional programming library for TypeScript, drawing inspiration from functional programming paradigms, the Scala Standard Library, and ZIO. It provides a comprehensive set of utilities and abstractions designed to facilitate functional programming within TypeScript applications.
9
-
10
- [API Documentation](https://jordanburke.github.io/functype/)
11
-
12
- ## Core Principles
13
-
14
- - **Immutability**: All data structures are immutable, promoting predictable and side-effect-free code
15
- - **Type Safety**: Leverages TypeScript's type system to ensure compile-time safety
16
- - **Composability**: Provides abstractions for building complex programs from simple components
17
- - **Functional Paradigms**: Embraces concepts like monads, functors, and type classes
18
- - **Unified Interface**: All data structures implement a common hierarchy of interfaces for consistency
19
-
20
- ## Key Features
21
-
22
- - **Option Type**: Handle nullable values with `Some` and `None` types
23
- - **Either Type**: Express computation results with potential failures using `Left` and `Right`
24
- - **List, Set, Map**: Immutable collection types with functional operators
25
- - **Try Type**: Safely execute operations that might throw exceptions
26
- - **Do-notation**: Scala-like for-comprehensions with **optimized List performance** (up to 12x faster than traditional flatMap)
27
- - **Task**: Handle synchronous and asynchronous operations with error handling
28
- - **Lazy**: Deferred computation with memoization
29
- - **Tuple**: Type-safe fixed-length arrays
30
- - **Typeable**: Runtime type identification with compile-time safety
31
- - **Branded Types**: Nominal typing in TypeScript's structural type system
32
- - **FPromise**: Enhanced Promise functionality with built-in error handling
33
- - **Error Formatting**: Utilities for improved error visualization and logging
34
- - **Unified Type Classes**: Consistent interfaces across all data structures
35
-
36
- ## Installation
37
-
38
- ```bash
39
- # NPM
40
- npm install functype
41
-
42
- # Yarn
43
- yarn add functype
44
-
45
- # PNPM
46
- pnpm add functype
47
-
48
- # Bun
49
- bun add functype
50
- ```
51
-
52
- ### Bundle Size Optimization
53
-
54
- Functype is optimized for tree-shaking and offers multiple import strategies to minimize bundle size:
55
-
56
- ```typescript
57
- // Selective module imports (recommended for production)
58
- import { Option } from "functype/option"
59
- import { Either } from "functype/either"
60
-
61
- // Direct constructor imports (smallest bundle)
62
- import { some, none } from "functype/option"
63
- ```
64
-
65
- For detailed optimization strategies, see the [Bundle Optimization Guide](docs/BUNDLE_OPTIMIZATION.md).
66
-
67
- ## Usage Examples
68
-
69
- ### Option
70
-
71
- ```typescript
72
- // Create options
73
- const value = Option("hello") // Some("hello")
74
- const empty = Option(null) // None
75
-
76
- // Transform values
77
- const upper = value.map((s) => s.toUpperCase()) // Some("HELLO")
78
- const _nothing = empty.map((s) => s.toUpperCase()) // None
79
-
80
- // Chain operations
81
- const result = value
82
- .map((s) => s.length)
83
- .filter((len) => len > 3)
84
- .orElse(0) // 5
85
-
86
- // Pattern matching
87
- const message = value.fold(
88
- () => "No value",
89
- (s) => `Value: ${s}`,
90
- ) // "Value: hello"
91
- ```
92
-
93
- ### Either
94
-
95
- ```typescript
96
- // Success case
97
- const success = Right<string, number>(42)
98
- // Error case
99
- const failure = Left<string, number>("Error occurred")
100
-
101
- // Transform success values only
102
- const doubled = success.map((n) => n * 2) // Right(84)
103
- const _failed = failure.map((n) => n * 2) // Left("Error occurred")
104
-
105
- // Handle both cases
106
- const result = success.fold(
107
- (error) => `Failed: ${error}`,
108
- (value) => `Success: ${value}`,
109
- ) // "Success: 42"
110
-
111
- // Chain operations that might fail
112
- const divide = (a: number, b: number) => (b === 0 ? Left("Division by zero") : Right(a / b))
113
-
114
- const calculation = Right(10)
115
- .flatMap((n) => divide(n, 2))
116
- .flatMap((n) => divide(n, 5)) // Right(1)
117
- ```
118
-
119
- ### List
120
-
121
- ```typescript
122
- import { List } from "functype"
123
-
124
- const numbers = List([1, 2, 3, 4])
125
-
126
- // Transform
127
- const doubled = numbers.map((x) => x * 2) // List([2, 4, 6, 8])
128
-
129
- // Filter
130
- const evens = numbers.filter((x) => x % 2 === 0) // List([2, 4])
131
-
132
- // Reduce
133
- const sum = numbers.foldLeft(0)((acc, x) => acc + x) // 10
134
-
135
- // Add/remove elements (immutably)
136
- const withFive = numbers.add(5) // List([1, 2, 3, 4, 5])
137
- const without3 = numbers.remove(3) // List([1, 2, 4])
138
-
139
- // Universal container operations
140
- const hasEven = numbers.exists((x) => x % 2 === 0) // true
141
- const firstEven = numbers.find((x) => x % 2 === 0) // Some(2)
142
- const evenCount = numbers.count((x) => x % 2 === 0) // 2
143
- ```
144
-
145
- ### Try
146
-
147
- ```typescript
148
- import { Try } from "functype"
149
-
150
- // Safely execute code that might throw
151
- const result = Try(() => {
152
- // Potentially throwing operation
153
- return JSON.parse('{"name": "John"}')
154
- })
155
-
156
- // Handle success/failure
157
- if (result.isSuccess()) {
158
- console.log("Result:", result.get())
159
- } else {
160
- console.error("Error:", result.error)
161
- }
162
-
163
- // Transform with map (only applies on Success)
164
- const name = result.map((obj) => obj.name)
165
-
166
- // Convert to Either
167
- const either = result.toEither()
168
- ```
169
-
170
- ### Lazy
171
-
172
- ```typescript
173
- import { Lazy } from "functype"
174
-
175
- // Create lazy computations
176
- const expensive = Lazy(() => {
177
- console.log("Computing...")
178
- return Math.random() * 1000
179
- })
180
-
181
- // Value is computed on first access and memoized
182
- const value1 = expensive.get() // Logs "Computing...", returns number
183
- const value2 = expensive.get() // Returns same number, no log
184
-
185
- // Transform lazy values
186
- const doubled = expensive.map((x) => x * 2)
187
- const formatted = doubled.map((x) => `Value: ${x}`)
188
-
189
- // Chain computations
190
- const result = Lazy(() => 10)
191
- .flatMap((x) => Lazy(() => x + 5))
192
- .map((x) => x * 2)
193
- .get() // 30
194
- ```
195
-
196
- ### Do-notation (High-Performance For-Comprehensions)
197
-
198
- Functype provides generator-based Do-notation for monadic composition, similar to Scala's for-comprehensions, with **significant performance advantages for List operations**:
199
-
200
- ```typescript
201
- import { Do, DoAsync, $ } from "functype"
202
- import { Option, Right, Left, List, Try } from "functype"
203
-
204
- // Chain multiple Option operations
205
- const result = Do(function* () {
206
- const x = yield* $(Option(5)) // Extract value from Option
207
- const y = yield* $(Option(10)) // Extract value from another Option
208
- const z = x + y // Regular computation
209
- return z * 2 // Return final result
210
- })
211
- // result: Option<number> with value 30
212
-
213
- // Mix different monad types (with Reshapeable)
214
- const mixed = Do(function* () {
215
- const a = yield* $(Option(5)) // From Option
216
- const b = yield* $(Right<string, number>(10)) // From Either
217
- const c = yield* $(List([15])) // From List
218
- const d = yield* $(Try(() => 20)) // From Try
219
- return a + b + c + d
220
- })
221
- // Convert result to desired type
222
- const asOption = mixed.toOption() // Option<number> with value 50
223
-
224
- // Error propagation - short-circuits on failure
225
- const validation = Do(function* () {
226
- const email = yield* $(validateEmail("user@example.com")) // Returns Option
227
- const user = yield* $(fetchUser(email)) // Returns Either
228
- const profile = yield* $(loadProfile(user.id)) // Returns Try
229
- return profile
230
- })
231
- // If any step fails, the entire computation short-circuits
232
-
233
- // List comprehensions - up to 12x FASTER than traditional flatMap!
234
- const pairs = Do(function* () {
235
- const x = yield* $(List([1, 2, 3]))
236
- const y = yield* $(List([10, 20]))
237
- return { x, y, product: x * y }
238
- })
239
- // pairs: List with 6 elements (all combinations)
240
-
241
- // Performance comparison:
242
- // Traditional: list.flatMap(x => list.flatMap(y => List([{x, y}]))) - slower
243
- // Do-notation: 2.5x to 12x faster for cartesian products!
244
-
245
- // Async operations with DoAsync
246
- const asyncResult = await DoAsync(async function* () {
247
- const user = yield* $(await fetchUserAsync(userId)) // Async Option
248
- const score = yield* $(await getScoreAsync(user.id)) // Async Either
249
- const bonus = yield* $(await calculateBonus(score)) // Async Try
250
- return score + bonus
251
- })
252
- ```
253
-
254
- **Performance Advantages:**
255
-
256
- - **List Comprehensions**: 2.5x to 12x faster than nested flatMap chains
257
- - **Optimized for Cartesian Products**: Efficient handling of multiple List yields
258
- - **Smart Caching**: Constructor lookups cached after first type detection
259
- - **Inline Helpers**: Reduced overhead from repeated type checks
260
-
261
- **When to Use Do-notation:**
262
-
263
- ✅ **Best for:**
264
-
265
- - Complex List comprehensions (huge performance win!)
266
- - Cartesian products and filtered combinations
267
- - Mixed monad types (leveraging Reshapeable)
268
- - Improved readability for multi-step operations
269
-
270
- ⚠️ **Consider alternatives for:**
271
-
272
- - Simple 2-3 step Option/Either chains (traditional flatMap is ~2x faster)
273
- - Performance-critical hot paths with simple monads
274
- - Early termination scenarios (flatMap auto-short-circuits more efficiently)
275
-
276
- **Key Differences from Scala:**
277
-
278
- - Uses `yield* $(monad)` instead of `x <- monad`
279
- - No native guard syntax (use conditions with early return)
280
- - Always returns the type of the first yielded monad
281
- - Mixed types supported via Reshapeable interface
282
-
283
- ### Task
284
-
285
- Task v2 provides structured error handling with the **Ok/Err pattern**, returning `TaskOutcome<T>` for all operations:
286
-
287
- ```typescript
288
- import { Task, Ok, Err, type TaskOutcome } from "functype"
289
-
290
- // Task v2: All operations return TaskOutcome<T>
291
- const syncResult = Task().Sync(() => "success")
292
- // Returns: TaskSuccess<string> (extends TaskOutcome<string>)
293
-
294
- const asyncResult = await Task().Async(async () => "value")
295
- // Returns: TaskOutcome<string>
296
-
297
- // Explicit Ok/Err returns for precise control
298
- const explicitResult = await Task().Async(async (): Promise<TaskOutcome<string>> => {
299
- if (Math.random() > 0.5) {
300
- return Ok("success") // Explicit success
301
- }
302
- return Err<string>("failed") // Explicit failure
303
- })
304
-
305
- // Auto-wrapping: raw values become Ok, thrown errors become Err
306
- const autoWrapped = await Task().Async(async () => {
307
- if (condition) {
308
- return "raw value" // Auto-wrapped as Ok("raw value")
309
- }
310
- throw new Error("failed") // Auto-wrapped as Err(error)
311
- })
312
-
313
- // Error recovery: error handlers can return Ok
314
- const recovered = await Task().Async(
315
- async () => {
316
- throw new Error("initial error")
317
- },
318
- async (error) => Ok("recovered from error"), // Recovery!
319
- )
320
-
321
- // Working with results
322
- if (asyncResult.isSuccess()) {
323
- console.log(asyncResult.value) // Access the success value
324
- } else {
325
- console.error(asyncResult.error) // Access the error (Throwable)
326
- }
327
-
328
- // Chaining with TaskOutcome
329
- const chainedResult = await Task().Async(async () => {
330
- const firstResult = await Task().Async(async () => "first")
331
- if (firstResult.isFailure()) {
332
- return firstResult // Propagate failure
333
- }
334
-
335
- const secondResult = await Task().Async(async () => "second")
336
- if (secondResult.isFailure()) {
337
- return secondResult
338
- }
339
-
340
- return Ok(`${firstResult.value} + ${secondResult.value}`)
341
- })
342
-
343
- // Converting promise-based functions to Task
344
- const fetchUserAPI = (userId: string): Promise<User> => fetch(`/api/users/${userId}`).then((r) => r.json())
345
-
346
- const fetchUser = Task.fromPromise(fetchUserAPI)
347
- // Returns: (userId: string) => FPromise<TaskOutcome<User>>
348
-
349
- const userResult = await fetchUser("user123")
350
- if (userResult.isSuccess()) {
351
- console.log(userResult.value) // User object
352
- }
353
-
354
- // Convert TaskOutcome back to Promise (for interop)
355
- const promise = Task.toPromise(asyncResult)
356
- // Success → resolves with value
357
- // Failure → rejects with error
358
- ```
359
-
360
- ### Branded Types
361
-
362
- ```typescript
363
- import { Brand, ValidatedBrand } from "functype/branded"
364
-
365
- // Create branded types for stronger type safety
366
- type UserId = Brand<"UserId", string>
367
- type Email = Brand<"Email", string>
368
-
369
- // Simple branding - branded values ARE primitives!
370
- const userId = Brand("UserId", "U123456")
371
- console.log(userId) // "U123456" - it IS a string
372
- console.log(typeof userId) // "string"
373
- console.log(userId.toUpperCase()) // "U123456" - string methods work!
374
-
375
- // Runtime-validated branding for safer input handling
376
- const EmailValidator = ValidatedBrand("Email", (s: string) => /^[^@]+@[^@]+\.[^@]+$/.test(s))
377
- const UserIdValidator = ValidatedBrand("UserId", (s: string) => /^U\d{6}$/.test(s))
378
-
379
- // Safe creation with Option/Either return types
380
- const email = EmailValidator.of("user@example.com") // Some(Brand<"Email", string>)
381
- const invalidEmail = EmailValidator.of("invalid") // None
382
-
383
- const userResult = UserIdValidator.from("U123456") // Right(Brand<"UserId", string>)
384
- const userError = UserIdValidator.from("invalid") // Left("Invalid UserId: validation failed")
385
-
386
- // Type safety in action
387
- function getUserByEmail(email: Email): User {
388
- /* ... */
389
- }
390
-
391
- // These calls are type-safe
392
- const userId = UserId("U123456")
393
- const email = Email("user@example.com")
394
- const user = getUserByEmail(email) // Works
395
-
396
- // These would be type errors
397
- getUserByEmail("invalid") // Type error: Argument of type 'string' is not assignable to parameter of type 'Email'
398
- getUserByEmail(userId) // Type error: Argument of type 'UserId' is not assignable to parameter of type 'Email'
399
- ```
400
-
401
- ## Conditional Programming
402
-
403
- Functype provides `Cond` and `Match` for functional conditional logic without early returns:
404
-
405
- ### Cond
406
-
407
- ```typescript
408
- import { Cond } from "functype"
409
-
410
- // Replace if-else chains with Cond
411
- const grade = Cond<number, string>()
412
- .case((score) => score >= 90, "A")
413
- .case((score) => score >= 80, "B")
414
- .case((score) => score >= 70, "C")
415
- .case((score) => score >= 60, "D")
416
- .default("F")
417
-
418
- console.log(grade(85)) // "B"
419
- console.log(grade(55)) // "F"
420
-
421
- // With transformation
422
- const discount = Cond<number, number>()
423
- .case(
424
- (qty) => qty >= 100,
425
- (qty) => qty * 0.2, // 20% off for 100+
426
- )
427
- .case(
428
- (qty) => qty >= 50,
429
- (qty) => qty * 0.1, // 10% off for 50+
430
- )
431
- .case(
432
- (qty) => qty >= 10,
433
- (qty) => qty * 0.05, // 5% off for 10+
434
- )
435
- .default(0)
436
-
437
- console.log(discount(150)) // 30 (20% of 150)
438
- ```
439
-
440
- ### Match
441
-
442
- ```typescript
443
- import { Match } from "functype"
444
-
445
- // Pattern matching with Match
446
- type Status = "pending" | "approved" | "rejected" | "cancelled"
447
-
448
- const statusMessage = Match<Status, string>()
449
- .case("pending", "Your request is being processed")
450
- .case("approved", "Your request has been approved!")
451
- .case("rejected", "Sorry, your request was rejected")
452
- .case("cancelled", "Your request was cancelled")
453
- .exhaustive()
454
-
455
- console.log(statusMessage("approved")) // "Your request has been approved!"
456
-
457
- // Match with predicates
458
- const numberType = Match<number, string>()
459
- .case(0, "zero")
460
- .case((n) => n > 0, "positive")
461
- .case((n) => n < 0, "negative")
462
- .exhaustive()
463
-
464
- console.log(numberType(42)) // "positive"
465
- console.log(numberType(-5)) // "negative"
466
- ```
467
-
468
- ### Advanced Pattern Matching
469
-
470
- Match supports exhaustive matching, nested patterns, and guards:
471
-
472
- ```typescript
473
- import { Match } from "functype"
474
-
475
- // Exhaustive matching with compile-time checking
476
- type Status = "idle" | "loading" | "success" | "error"
477
- const result = Match<Status, string>("success")
478
- .case("idle", "Waiting...")
479
- .case("loading", "Loading...")
480
- .case("success", "Done!")
481
- .case("error", "Failed!")
482
- .exhaustive() // Compile error if any case is missing
483
-
484
- // Nested pattern matching
485
- type User = {
486
- name: string
487
- age: number
488
- role: "admin" | "user"
489
- preferences?: { theme: "light" | "dark" }
490
- }
491
-
492
- const message = Match<User, string>(user)
493
- .case({ role: "admin", age: (n) => n >= 18, preferences: { theme: "dark" } }, "Adult admin with dark mode")
494
- .case({ role: "user" }, (u) => `Regular user: ${u.name}`)
495
- .when((u) => u.age < 18, "Minor user - restricted access")
496
- .default("Unknown user type")
497
-
498
- // Reusable pattern matchers
499
- const classifier = Match.builder<Animal, string>()
500
- .when((a) => a.canFly, "Flying creature")
501
- .case({ legs: 0 }, "Legless")
502
- .case({ legs: 2 }, "Biped")
503
- .case({ legs: 4 }, "Quadruped")
504
- .default("Other")
505
- .build()
506
- ```
507
-
508
- ## Fold
509
-
510
- Functype includes a powerful `fold` operation for pattern matching and extracting values:
511
-
512
- ```typescript
513
- import { Option, Either, Try, List } from "functype"
514
-
515
- // Option fold
516
- const opt = Option(5)
517
- const optResult = opt.fold(
518
- () => "None",
519
- (value) => `Some(${value})`,
520
- ) // "Some(5)"
521
-
522
- // Either fold
523
- const either = Right<string, number>(42)
524
- const eitherResult = either.fold(
525
- (left) => `Left(${left})`,
526
- (right) => `Right(${right})`,
527
- ) // "Right(42)"
528
-
529
- // Try fold
530
- const tryValue = Try(() => 10)
531
- const tryResult = tryValue.fold(
532
- (error) => `Error: ${error.message}`,
533
- (value) => `Success: ${value}`,
534
- ) // "Success: 10"
535
-
536
- // List fold
537
- const list = List([1, 2, 3])
538
- const listResult = list.foldLeft(0)((acc, num) => acc + num) // 6
539
- ```
540
-
541
- ## Foldable
542
-
543
- Functype includes a `Foldable` type class that all data structures implement:
544
-
545
- ```typescript
546
- import { FoldableUtils, Option, List, Try } from "functype"
547
-
548
- // All data structures implement the Foldable interface
549
- const option = Option(5)
550
- const list = List([1, 2, 3, 4, 5])
551
- const tryVal = Try(() => 10)
552
-
553
- // Use fold to pattern-match on data structures
554
- option.fold(
555
- () => console.log("Empty option"),
556
- (value) => console.log(`Option value: ${value}`),
557
- )
558
-
559
- // Use foldLeft for left-associative operations
560
- const sum = list.foldLeft(0)((acc, value) => acc + value) // 15
561
-
562
- // Use foldRight for right-associative operations
563
- const product = list.foldRight(1)((value, acc) => value * acc) // 120
564
-
565
- // Use FoldableUtils to work with any Foldable
566
- const isEmpty = FoldableUtils.isEmpty(option) // false
567
- const size = FoldableUtils.size(list) // 5
568
- const convertedToList = FoldableUtils.toList(option) // List([5])
569
- const convertedToEither = FoldableUtils.toEither(tryVal, "Error") // Right(10)
570
- ```
571
-
572
- ## Matchable
573
-
574
- Functype includes a `Matchable` type class for enhanced pattern matching:
575
-
576
- ```typescript
577
- import { Option, Either, Try, List, MatchableUtils } from "functype"
578
-
579
- // Pattern matching on Option
580
- const opt = Option(42)
581
- const optResult = opt.match({
582
- Some: (value) => `Found: ${value}`,
583
- None: () => "Not found",
584
- }) // "Found: 42"
585
-
586
- // Pattern matching on Either
587
- const either = Either.fromNullable(null, "Missing value")
588
- const eitherResult = either.match({
589
- Left: (error) => `Error: ${error}`,
590
- Right: (value) => `Value: ${value}`,
591
- }) // "Error: Missing value"
592
-
593
- // Pattern matching on Try
594
- const tryVal = Try(() => JSON.parse('{"name":"John"}'))
595
- const tryResult = tryVal.match({
596
- Success: (data) => `Name: ${data.name}`,
597
- Failure: (error) => `Parse error: ${error.message}`,
598
- }) // "Name: John"
599
-
600
- // Pattern matching on List
601
- const list = List([1, 2, 3])
602
- const listResult = list.match({
603
- NonEmpty: (values) => `Values: ${values.join(", ")}`,
604
- Empty: () => "No values",
605
- }) // "Values: 1, 2, 3"
606
-
607
- // Using MatchableUtils for advanced pattern matching
608
- const isPositive = MatchableUtils.when(
609
- (n: number) => n > 0,
610
- (n) => `Positive: ${n}`,
611
- )
612
-
613
- const defaultCase = MatchableUtils.default((n: number) => `Default: ${n}`)
614
-
615
- // Using pattern guards in custom matching logic
616
- const num = 42
617
- const result = isPositive(num) ?? defaultCase(num) // "Positive: 42"
618
- ```
619
-
620
- ## Interface Hierarchy
621
-
622
- All data structures in Functype implement a unified hierarchy of interfaces, providing consistent behavior across the library:
623
-
624
- ### Type Classes
625
-
626
- Functype leverages type classes to provide common operations:
627
-
628
- - **Functor**: Supports `map` operation for transforming wrapped values
629
- - **Applicative**: Extends Functor with `ap` for applying wrapped functions
630
- - **Monad**: Extends Applicative with `flatMap` for chaining operations
631
- - **AsyncMonad**: Extends Monad with `flatMapAsync` for async operations
632
- - **ContainerOps**: Universal operations for all containers (single-value and collections)
633
- - **CollectionOps**: Operations specific to collections like List and Set
634
-
635
- ### Unified Interfaces
636
-
637
- All data structures implement the `Functype` hierarchy:
638
-
639
- ```typescript
640
- // Base interface for all data structures
641
- interface FunctypeBase<A, Tag>
642
- extends AsyncMonad<A>, Traversable<A>, Serializable<A>, Foldable<A>, Typeable<Tag>, ContainerOps<A> {
643
- readonly _tag: Tag
644
- }
645
-
646
- // For single-value containers (Option, Either, Try)
647
- interface Functype<A, Tag> extends FunctypeBase<A, Tag>, Extractable<A>, Pipe<A>, Matchable<A, Tag> {
648
- toValue(): { _tag: Tag; value: A }
649
- }
650
-
651
- // For collections (List, Set, Map)
652
- interface FunctypeCollection<A, Tag>
653
- extends FunctypeBase<A, Tag>, Iterable<A>, Pipe<A[]>, Collection<A>, CollectionOps<A, FunctypeCollection<A, Tag>> {
654
- toValue(): { _tag: Tag; value: A[] }
655
- // Collections work with Iterable instead of Monad
656
- flatMap<B>(f: (value: A) => Iterable<B>): FunctypeCollection<B, Tag>
657
- }
658
- ```
659
-
660
- ### Container Operations
661
-
662
- All containers (Option, Either, Try, List, Set) support these universal operations:
663
-
664
- ```typescript
665
- import { Option, List } from "functype"
666
-
667
- const opt = Option(42)
668
- const list = List([1, 2, 3, 4, 5])
669
-
670
- // Universal operations work on both single-value and collections
671
- opt.count((x) => x > 40) // 1
672
- list.count((x) => x > 3) // 2
673
-
674
- opt.find((x) => x > 40) // Some(42)
675
- list.find((x) => x > 3) // Some(4)
676
-
677
- opt.exists((x) => x === 42) // true
678
- list.exists((x) => x === 3) // true
679
-
680
- opt.forEach(console.log) // Logs: 42
681
- list.forEach(console.log) // Logs: 1, 2, 3, 4, 5
682
- ```
683
-
684
- ## Feature Matrix
685
-
686
- For a comprehensive overview of which interfaces are supported by each data structure, see the [Functype Feature Matrix](docs/FUNCTYPE_FEATURE_MATRIX.md).
687
-
688
- ## Type Safety
689
-
690
- Functype leverages TypeScript's advanced type system to provide compile-time safety for functional patterns, ensuring that your code is both robust and maintainable.
691
-
692
- ```typescript
693
- // Type inference works seamlessly
694
- const option = Option(42)
695
- // Inferred as number
696
- const mappedValue = option.map((x) => x.toString())
697
- // Inferred as string
698
- ```
699
-
700
- ## Error Formatting
701
-
702
- Functype provides utilities for improved error visualization and logging:
703
-
704
- ```typescript
705
- import { formatError, createErrorSerializer } from "functype/error"
706
-
707
- // Create a nested task error
708
- const innerTask = Task({ name: "DbQuery" }).Sync(() => {
709
- throw new Error("Database connection failed")
710
- })
711
-
712
- const outerTask = Task({ name: "UserFetch" }).Sync(() => {
713
- return innerTask.value
714
- })
715
-
716
- // Format the error for console display
717
- console.error(
718
- formatError(outerTask.value as Error, {
719
- includeTasks: true,
720
- includeStackTrace: true,
721
- colors: true,
722
- }),
723
- )
724
-
725
- // Create a serializer for structured logging libraries like Pino
726
- const errorSerializer = createErrorSerializer()
727
-
728
- // Use with Pino
729
- const logger = pino({
730
- serializers: { err: errorSerializer },
731
- })
732
-
733
- // Log the error with full context
734
- logger.error(
735
- {
736
- err: outerTask.value,
737
- requestId: "req-123",
738
- },
739
- "Failed to fetch user data",
740
- )
741
- ```
742
-
743
- For more details, see the [Error Formatting Guide](docs/error-formatting.md).
744
-
745
- ## Roadmap / TODO
746
-
747
- ### High Priority
748
-
749
- - [x] Complete LazyList Implementation
750
- - ✓ Add Foldable interface (fold, foldLeft, foldRight)
751
- - ✓ Add Pipe interface for composition
752
- - ✓ Add Serializable for persistence
753
- - ✓ Add Typeable support
754
- - [ ] Implement NonEmptyList<A>
755
- - List guaranteed to have at least one element
756
- - Prevents empty list errors at compile time
757
- - Full standard interface implementation
758
- - Methods like `head` return `A` instead of `Option<A>`
759
-
760
- ### Medium Priority
761
-
762
- - [ ] Implement ValidatedNel<E, A> for validation with error accumulation
763
- - Unlike Either, collects multiple errors
764
- - Uses NonEmptyList for error collection
765
- - Applicative instance combines errors
766
- - [x] Enhance Pattern Matching
767
- - ✓ Add exhaustiveness checking at compile time
768
- - ✓ Support nested pattern matching
769
- - ✓ Add guard clauses (when conditions)
770
- - ✓ Support destructuring patterns
771
- - ✓ Consolidated into unified Match implementation
772
- - [ ] Implement IO<A> monad for functional side effects
773
- - Lazy execution of effects
774
- - Composable IO operations
775
- - Integration with Task for async IO
776
-
777
- ### Low Priority
778
-
779
- - [x] Complete Tuple Implementation
780
- - ✓ Add Foldable for tuple operations
781
- - ✓ Add Pipe interface for composition
782
- - ✓ Add Serializable for persistence
783
- - ✓ Add Companion pattern with utility methods
784
- - ✓ Added specialized pair() and triple() constructors
785
- - [ ] Implement Lens<S, A> for immutable updates
786
- - Composable property access
787
- - Type-safe nested updates
788
- - Works with all functype data structures
789
- - [ ] Add Reader/State monads for dependency injection and state management
790
-
791
- ### Completed Functionality
792
-
793
- - [x] Add lazy evaluation structures (LazyList implemented, needs interface completion)
794
- - [x] Add a proper Foldable type class interface
795
- - [x] Implement Matchable type class for pattern matching
796
- - [x] Implement Applicative and other functional type classes (for most types)
797
-
798
- ### Performance Optimizations
799
-
800
- - [ ] Add memoization utilities
801
- - [ ] Improve recursive operations for large collections
802
- - [ ] Implement immutable data structures with structural sharing
803
- - [ ] Add performance benchmarks
804
- - [x] Optimize TreeShaking with sideEffects flag in package.json
805
- - [x] Support selective module imports for smaller bundles
806
- - [x] Add bundle size monitoring to CI/CD
807
-
808
- ### API Consistency
809
-
810
- - [ ] Ensure all modules follow the Scala-inspired pattern:
811
- - Constructor functions that return objects with methods
812
- - Object methods for common operations
813
- - Companion functions for additional utilities
814
- - [x] Align Task API with other monadic structures
815
- - [ ] Standardize import patterns (@ imports vs relative paths)
816
- - [x] Implement consistent error handling strategy for async operations
817
-
818
- ### Testing and Documentation
819
-
820
- - [ ] Add observable test coverage metrics
821
- - [x] Implement property-based testing
822
- - [ ] Expand error handling tests
823
- - [ ] Add interoperability tests with other libraries
824
-
825
- ### TypeScript Improvements
826
-
827
- - [x] Enable stricter TypeScript settings (noImplicitAny: true)
828
- - [x] Add noUncheckedIndexedAccess for safer array indexing
829
- - [ ] Improve support for higher-kinded types:
830
- - Current type parameters work well for first-order types
831
- - Expand to support type constructors as parameters (F<A> => F<B>)
832
- - [x] Add branded/nominal types for stronger type safety
833
- - [ ] Implement more type-level utilities (conditional types, template literals)
834
- - [ ] Leverage newer TypeScript features (const type parameters, tuple manipulation)
835
-
836
- ## Contributing
837
-
838
- Contributions are welcome! Please feel free to submit a Pull Request.
839
-
840
- ## License
841
-
842
- MIT License
843
-
844
- Copyright (c) 2025 Jordan Burke
845
-
846
- Permission is hereby granted, free of charge, to any person obtaining a copy
847
- of this software and associated documentation files (the "Software"), to deal
848
- in the Software without restriction, including without limitation the rights
849
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
850
- copies of the Software, and to permit persons to whom the Software is
851
- furnished to do so, subject to the following conditions:
852
-
853
- The above copyright notice and this permission notice shall be included in all
854
- copies or substantial portions of the Software.
855
-
856
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
857
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
858
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
859
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
860
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
861
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
862
- SOFTWARE.