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.
- package/dist/Brand-BPeggBaO.d.ts +1 -2
- package/dist/Brand-Cfr5zy8F.js +1 -2
- package/dist/Tuple-C4maYbiO.d.ts +1 -2
- package/dist/Tuple-CgX4p79w.js +1 -2
- package/dist/cli/index.js +2 -3
- package/dist/do/index.d.ts +1 -1
- package/dist/do/index.js +1 -1
- package/dist/either/index.d.ts +1 -1
- package/dist/either/index.js +1 -1
- package/dist/fpromise/index.d.ts +1 -1
- package/dist/fpromise/index.js +1 -1
- package/dist/{index-Bn_yRBx8.d.ts → index-B6Civ4kr.d.ts} +19 -9
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/list/index.d.ts +1 -1
- package/dist/list/index.js +1 -1
- package/dist/map/index.d.ts +1 -1
- package/dist/map/index.js +1 -1
- package/dist/option/index.d.ts +1 -1
- package/dist/option/index.js +1 -1
- package/dist/set/index.d.ts +1 -1
- package/dist/set/index.js +1 -1
- package/dist/{src-JcsaR9MX.js → src-DpfaJv6K.js} +2 -3
- package/dist/try/index.d.ts +1 -1
- package/dist/try/index.js +1 -1
- package/package.json +35 -38
- package/README.processed.md +0 -862
- package/dist/Brand-Cfr5zy8F.js.map +0 -1
- package/dist/Tuple-CgX4p79w.js.map +0 -1
- package/dist/cli/index.js.map +0 -1
- package/dist/src-JcsaR9MX.js.map +0 -1
- package/readme/BRAND_MIGRATION_GUIDE.md +0 -230
- package/readme/BUNDLE_OPTIMIZATION.md +0 -74
- package/readme/FPromise-Assessment.md +0 -43
- package/readme/HKT.md +0 -110
- package/readme/ROADMAP.md +0 -113
- package/readme/TASK-TODO.md +0 -33
- package/readme/TUPLE-EXAMPLES.md +0 -76
- package/readme/TaskMigration.md +0 -129
- package/readme/ai-guide.md +0 -406
- package/readme/examples.md +0 -2093
- package/readme/functype-changes-required.md +0 -189
- package/readme/quick-reference.md +0 -514
- package/readme/task-error-handling.md +0 -283
- package/readme/tasks.md +0 -203
- package/readme/type-index.md +0 -238
package/README.processed.md
DELETED
|
@@ -1,862 +0,0 @@
|
|
|
1
|
-
# Functype
|
|
2
|
-
|
|
3
|
-

|
|
4
|
-
[](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.
|