functype 0.11.2 → 0.14.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/README.md CHANGED
@@ -23,6 +23,7 @@ Functype is a lightweight functional programming library for TypeScript, drawing
23
23
  - **Either Type**: Express computation results with potential failures using `Left` and `Right`
24
24
  - **List, Set, Map**: Immutable collection types with functional operators
25
25
  - **Try Type**: Safely execute operations that might throw exceptions
26
+ - **Do-notation**: Scala-like for-comprehensions using JavaScript generators for monadic composition
26
27
  - **Task**: Handle synchronous and asynchronous operations with error handling
27
28
  - **Lazy**: Deferred computation with memoization
28
29
  - **Tuple**: Type-safe fixed-length arrays
@@ -189,40 +190,142 @@ const result = Lazy(() => 10)
189
190
  .get() // 30
190
191
  ```
191
192
 
193
+ ### Do-notation (Scala-like For-Comprehensions)
194
+
195
+ Functype provides generator-based Do-notation for monadic composition, similar to Scala's for-comprehensions:
196
+
197
+ ```typescript
198
+ import { Do, DoAsync, $ } from "functype"
199
+ import { Option, Right, Left, List, Try } from "functype"
200
+
201
+ // Chain multiple Option operations
202
+ const result = Do(function* () {
203
+ const x = yield* $(Option(5)) // Extract value from Option
204
+ const y = yield* $(Option(10)) // Extract value from another Option
205
+ const z = x + y // Regular computation
206
+ return z * 2 // Return final result
207
+ })
208
+ // result: Option<number> with value 30
209
+
210
+ // Mix different monad types (with Reshapeable)
211
+ const mixed = Do(function* () {
212
+ const a = yield* $(Option(5)) // From Option
213
+ const b = yield* $(Right<string, number>(10)) // From Either
214
+ const c = yield* $(List([15])) // From List
215
+ const d = yield* $(Try(() => 20)) // From Try
216
+ return a + b + c + d
217
+ })
218
+ // Convert result to desired type
219
+ const asOption = mixed.toOption() // Option<number> with value 50
220
+
221
+ // Error propagation - short-circuits on failure
222
+ const validation = Do(function* () {
223
+ const email = yield* $(validateEmail("user@example.com")) // Returns Option
224
+ const user = yield* $(fetchUser(email)) // Returns Either
225
+ const profile = yield* $(loadProfile(user.id)) // Returns Try
226
+ return profile
227
+ })
228
+ // If any step fails, the entire computation short-circuits
229
+
230
+ // List comprehensions (cartesian products)
231
+ const pairs = Do(function* () {
232
+ const x = yield* $(List([1, 2, 3]))
233
+ const y = yield* $(List([10, 20]))
234
+ return { x, y, product: x * y }
235
+ })
236
+ // pairs: List with 6 elements (all combinations)
237
+
238
+ // Async operations with DoAsync
239
+ const asyncResult = await DoAsync(async function* () {
240
+ const user = yield* $(await fetchUserAsync(userId)) // Async Option
241
+ const score = yield* $(await getScoreAsync(user.id)) // Async Either
242
+ const bonus = yield* $(await calculateBonus(score)) // Async Try
243
+ return score + bonus
244
+ })
245
+ ```
246
+
247
+ **Key Differences from Scala:**
248
+
249
+ - Uses `yield* $(monad)` instead of `x <- monad`
250
+ - No native guard syntax (use conditions with early return)
251
+ - Always returns the type of the first yielded monad
252
+ - Mixed types supported via Reshapeable interface
253
+
192
254
  ### Task
193
255
 
256
+ Task v2 provides structured error handling with the **Ok/Err pattern**, returning `TaskOutcome<T>` for all operations:
257
+
194
258
  ```typescript
195
- import { Task } from "functype"
259
+ import { Task, Ok, Err, type TaskOutcome } from "functype"
196
260
 
197
- // Synchronous operations with error handling
198
- const syncResult = Task().Sync(
199
- () => "success",
200
- (error) => new Error(`Failed: ${error}`),
261
+ // Task v2: All operations return TaskOutcome<T>
262
+ const syncResult = Task().Sync(() => "success")
263
+ // Returns: TaskSuccess<string> (extends TaskOutcome<string>)
264
+
265
+ const asyncResult = await Task().Async(async () => "value")
266
+ // Returns: TaskOutcome<string>
267
+
268
+ // Explicit Ok/Err returns for precise control
269
+ const explicitResult = await Task().Async(async (): Promise<TaskOutcome<string>> => {
270
+ if (Math.random() > 0.5) {
271
+ return Ok("success") // Explicit success
272
+ }
273
+ return Err<string>("failed") // Explicit failure
274
+ })
275
+
276
+ // Auto-wrapping: raw values become Ok, thrown errors become Err
277
+ const autoWrapped = await Task().Async(async () => {
278
+ if (condition) {
279
+ return "raw value" // Auto-wrapped as Ok("raw value")
280
+ }
281
+ throw new Error("failed") // Auto-wrapped as Err(error)
282
+ })
283
+
284
+ // Error recovery: error handlers can return Ok
285
+ const recovered = await Task().Async(
286
+ async () => {
287
+ throw new Error("initial error")
288
+ },
289
+ async (error) => Ok("recovered from error"), // Recovery!
201
290
  )
202
291
 
203
- // Asynchronous operations
204
- const asyncTask = async () => {
205
- const result = await Task().Async(
206
- async () => await fetchData(),
207
- async (error) => new Error(`Fetch failed: ${error}`),
208
- )
209
- return result
292
+ // Working with results
293
+ if (asyncResult.isSuccess()) {
294
+ console.log(asyncResult.value) // Access the success value
295
+ } else {
296
+ console.error(asyncResult.error) // Access the error (Throwable)
210
297
  }
211
298
 
299
+ // Chaining with TaskOutcome
300
+ const chainedResult = await Task().Async(async () => {
301
+ const firstResult = await Task().Async(async () => "first")
302
+ if (firstResult.isFailure()) {
303
+ return firstResult // Propagate failure
304
+ }
305
+
306
+ const secondResult = await Task().Async(async () => "second")
307
+ if (secondResult.isFailure()) {
308
+ return secondResult
309
+ }
310
+
311
+ return Ok(`${firstResult.value} + ${secondResult.value}`)
312
+ })
313
+
212
314
  // Converting promise-based functions to Task
213
315
  const fetchUserAPI = (userId: string): Promise<User> => fetch(`/api/users/${userId}`).then((r) => r.json())
214
316
 
215
- // Use the adapter pattern for seamless integration
216
- const fetchUser = Task({ name: "UserFetch" }).fromPromise(fetchUserAPI)
317
+ const fetchUser = Task.fromPromise(fetchUserAPI)
318
+ // Returns: (userId: string) => FPromise<TaskOutcome<User>>
217
319
 
218
- // Later use it with standard promise patterns
219
- fetchUser("user123")
220
- .then((user) => console.log(user))
221
- .catch((error) => console.error(error))
320
+ const userResult = await fetchUser("user123")
321
+ if (userResult.isSuccess()) {
322
+ console.log(userResult.value) // User object
323
+ }
222
324
 
223
- // Or convert Task results back to promises
224
- const taskResult = Task().Sync(() => "hello world")
225
- const promise = Task().toPromise(taskResult) // Promise<string>
325
+ // Convert TaskOutcome back to Promise (for interop)
326
+ const promise = Task.toPromise(asyncResult)
327
+ // Success resolves with value
328
+ // Failure → rejects with error
226
329
  ```
227
330
 
228
331
  ### Branded Types
@@ -1,4 +1,85 @@
1
- import { T as Type, a as Typeable, S as Serializable, F as Foldable, P as Pipe } from './Serializable-CK9upOU0.js';
1
+ import { T as Type, P as Pipe, a as Typeable, S as Serializable, F as Foldable } from './Typeable-CitTP1ay.js';
2
+
3
+ /**
4
+ * Protocol definitions for Do-notation
5
+ * Separated from main Do module to avoid circular dependencies
6
+ */
7
+ /**
8
+ * Protocol symbol for Do-notation unwrapping
9
+ * All monads that support Do-notation should implement this protocol
10
+ */
11
+ declare const DO_PROTOCOL: unique symbol;
12
+ /**
13
+ * Result type for Do-notation unwrapping
14
+ * Indicates whether unwrapping succeeded and provides the value or error
15
+ */
16
+ type DoResult<T> = {
17
+ ok: true;
18
+ value: T;
19
+ } | {
20
+ ok: false;
21
+ empty: true;
22
+ } | {
23
+ ok: false;
24
+ empty: false;
25
+ error: unknown;
26
+ };
27
+ /**
28
+ * Interface for types that support Do-notation
29
+ * Implementing this interface allows a type to be yielded in Do-comprehensions
30
+ */
31
+ interface DoProtocol<T> {
32
+ [DO_PROTOCOL](): DoResult<T>;
33
+ }
34
+
35
+ /**
36
+ * Extractable type class for data structures that can extract their values
37
+ * with various fallback strategies.
38
+ *
39
+ * This interface is implemented by Option, Either, and other types that
40
+ * wrap values and need safe extraction methods.
41
+ */
42
+ interface Extractable<T extends Type> {
43
+ /**
44
+ * Extracts the value unsafely
45
+ * @throws Error if the container is empty
46
+ * @returns The contained value
47
+ */
48
+ get(): T;
49
+ /**
50
+ * Returns the contained value or a default value
51
+ * @param defaultValue - The value to return if extraction fails
52
+ * @returns The contained value or defaultValue
53
+ */
54
+ getOrElse(defaultValue: T): T;
55
+ /**
56
+ * Returns the contained value or throws an error
57
+ * @param error - Optional error to throw (implementations may have defaults)
58
+ * @returns The contained value
59
+ * @throws The specified error if extraction fails
60
+ */
61
+ getOrThrow(error?: Error): T;
62
+ /**
63
+ * Returns this container if it has a value, otherwise returns the alternative
64
+ * @param alternative - The alternative container
65
+ * @returns This container or the alternative
66
+ */
67
+ orElse(alternative: Extractable<T>): Extractable<T>;
68
+ /**
69
+ * Returns the contained value or null
70
+ * @returns The contained value or null
71
+ */
72
+ orNull(): T | null;
73
+ /**
74
+ * Returns the contained value or undefined
75
+ * @returns The contained value or undefined
76
+ */
77
+ orUndefined(): T | undefined;
78
+ }
79
+ /**
80
+ * Type guard to check if a value implements ExtractableOption
81
+ */
82
+ declare function isExtractable<T extends Type>(value: unknown): value is Extractable<T>;
2
83
 
3
84
  /**
4
85
  * Universal operations that work on any container (single-value or collection).
@@ -144,6 +225,164 @@ interface Promisable<A extends Type> {
144
225
  toPromise(): Promise<A>;
145
226
  }
146
227
 
228
+ /**
229
+ * Possible types of Try instances
230
+ */
231
+ type TypeNames = "Success" | "Failure";
232
+ interface Try<T> extends FunctypeBase<T, TypeNames>, Extractable<T>, Pipe<T>, Promisable<T>, DoProtocol<T>, Reshapeable<T> {
233
+ readonly _tag: TypeNames;
234
+ readonly error: Error | undefined;
235
+ isSuccess(): this is Try<T> & {
236
+ readonly _tag: "Success";
237
+ error: undefined;
238
+ };
239
+ isFailure(): this is Try<T> & {
240
+ readonly _tag: "Failure";
241
+ error: Error;
242
+ };
243
+ get: () => T;
244
+ getOrElse: (defaultValue: T) => T;
245
+ getOrThrow: (error?: Error) => T;
246
+ orElse: (alternative: Try<T>) => Try<T>;
247
+ orNull: () => T | null;
248
+ orUndefined: () => T | undefined;
249
+ orThrow: (error: Error) => T;
250
+ toOption: () => Option<T>;
251
+ toEither: <E extends Type>(leftValue: E) => Either<E, T>;
252
+ toList: () => List<T>;
253
+ toTry: () => Try<T>;
254
+ map: <U>(f: (value: T) => U) => Try<U>;
255
+ ap: <U>(ff: Try<(value: T) => U>) => Try<U>;
256
+ flatMap: <U>(f: (value: T) => Try<U>) => Try<U>;
257
+ flatMapAsync: <U>(f: (value: T) => Promise<Try<U>>) => Promise<Try<U>>;
258
+ /**
259
+ * Pattern matches over the Try, applying onFailure if Failure and onSuccess if Success
260
+ * @param onFailure - Function to apply if the Try is Failure
261
+ * @param onSuccess - Function to apply if the Try is Success
262
+ * @returns The result of applying the appropriate function
263
+ */
264
+ fold: <U extends Type>(onFailure: (error: Error) => U, onSuccess: (value: T) => U) => U;
265
+ toString: () => string;
266
+ /**
267
+ * Pattern matches over the Try, applying a handler function based on the variant
268
+ * @param patterns - Object with handler functions for Success and Failure variants
269
+ * @returns The result of applying the matching handler function
270
+ */
271
+ match<R>(patterns: {
272
+ Success: (value: T) => R;
273
+ Failure: (error: Error) => R;
274
+ }): R;
275
+ toValue(): {
276
+ _tag: TypeNames;
277
+ value: T | Error;
278
+ };
279
+ }
280
+ declare const Try: (<T>(f: () => T) => Try<T>) & {
281
+ /**
282
+ * Creates a Try from JSON string
283
+ * @param json - The JSON string
284
+ * @returns Try instance
285
+ */
286
+ fromJSON: <T>(json: string) => Try<T>;
287
+ /**
288
+ * Creates a Try from YAML string
289
+ * @param yaml - The YAML string
290
+ * @returns Try instance
291
+ */
292
+ fromYAML: <T>(yaml: string) => Try<T>;
293
+ /**
294
+ * Creates a Try from binary string
295
+ * @param binary - The binary string
296
+ * @returns Try instance
297
+ */
298
+ fromBinary: <T>(binary: string) => Try<T>;
299
+ };
300
+
301
+ /**
302
+ * Interface for types that can be reshaped (converted) between different monadic containers.
303
+ * Provides standard conversion methods to transform between Option, Either, List, and Try types.
304
+ *
305
+ * @typeParam T - The type of the value contained in the monad
306
+ *
307
+ * @example
308
+ * // Convert Option to Either
309
+ * const opt = Option(5)
310
+ * const either = opt.toEither("None value") // Right(5)
311
+ *
312
+ * @example
313
+ * // Convert Either to Option
314
+ * const right = Right(10)
315
+ * const option = right.toOption() // Some(10)
316
+ *
317
+ * @example
318
+ * // Convert List to Try
319
+ * const list = List([1, 2, 3])
320
+ * const tryVal = list.toTry() // Success(1) - uses first element
321
+ *
322
+ * @example
323
+ * // Use with Do comprehensions
324
+ * const result = Do(function* () {
325
+ * const x = yield* $(Option(5))
326
+ * const y = yield* $(Right<string, number>(10))
327
+ * return x + y
328
+ * })
329
+ *
330
+ * // Convert to desired type for chaining
331
+ * const asOption = result.toOption()
332
+ * asOption.map(x => x * 2).getOrElse(0)
333
+ */
334
+ interface Reshapeable<T extends Type> {
335
+ /**
336
+ * Converts this monad to an Option.
337
+ *
338
+ * Conversion rules:
339
+ * - Option: returns self
340
+ * - Either: Right → Some, Left → None
341
+ * - List: non-empty → Some(head), empty → None
342
+ * - Try: Success → Some, Failure → None
343
+ *
344
+ * @returns An Option containing the value if present, None otherwise
345
+ */
346
+ toOption(): Option<T>;
347
+ /**
348
+ * Converts this monad to an Either.
349
+ *
350
+ * Conversion rules:
351
+ * - Option: Some → Right, None → Left(leftValue)
352
+ * - Either: returns self
353
+ * - List: non-empty → Right(head), empty → Left(leftValue)
354
+ * - Try: Success → Right, Failure → Left(error)
355
+ *
356
+ * @param leftValue - The value to use for the Left case when the source is empty/none/failure
357
+ * @returns An Either with the value as Right or the provided leftValue as Left
358
+ */
359
+ toEither<E extends Type>(leftValue: E): Either<E, T>;
360
+ /**
361
+ * Converts this monad to a List.
362
+ *
363
+ * Conversion rules:
364
+ * - Option: Some → List([value]), None → List([])
365
+ * - Either: Right → List([value]), Left → List([])
366
+ * - List: returns self
367
+ * - Try: Success → List([value]), Failure → List([])
368
+ *
369
+ * @returns A List containing the value(s) if present, empty List otherwise
370
+ */
371
+ toList(): List<T>;
372
+ /**
373
+ * Converts this monad to a Try.
374
+ *
375
+ * Conversion rules:
376
+ * - Option: Some → Success, None → Failure(Error("None"))
377
+ * - Either: Right → Success, Left → Failure(Error(leftValue))
378
+ * - List: non-empty → Success(head), empty → Failure(Error("Empty list"))
379
+ * - Try: returns self
380
+ *
381
+ * @returns A Try containing Success with the value or Failure with an appropriate error
382
+ */
383
+ toTry(): Try<T>;
384
+ }
385
+
147
386
  /**
148
387
  * Creates a Some variant of Option containing a value.
149
388
  * @param value - The value to wrap in Some
@@ -175,7 +414,7 @@ declare const OptionConstructor: <T extends Type>(value: T | null | undefined) =
175
414
  * It's used to handle potentially null or undefined values in a type-safe way.
176
415
  * @typeParam T - The type of the value contained in the Option
177
416
  */
178
- interface Option<T extends Type> extends Functype<T, "Some" | "None">, Promisable<T> {
417
+ interface Option<T extends Type> extends Functype<T, "Some" | "None">, Promisable<T>, DoProtocol<T>, Reshapeable<T> {
179
418
  /** The contained value (undefined for None) */
180
419
  readonly value: T | undefined;
181
420
  /** Whether this Option contains no value */
@@ -368,7 +607,7 @@ declare const Option: (<T extends Type>(value: T | null | undefined) => Option<T
368
607
  fromBinary: <T>(binary: string) => Option<T>;
369
608
  };
370
609
 
371
- interface List<A> extends FunctypeCollection<A, "List"> {
610
+ interface List<A> extends FunctypeCollection<A, "List">, DoProtocol<A>, Reshapeable<A> {
372
611
  readonly length: number;
373
612
  readonly [Symbol.iterator]: () => Iterator<A>;
374
613
  map: <B>(f: (a: A) => B) => List<B>;
@@ -464,55 +703,6 @@ interface Collection<A> {
464
703
  toString(): string;
465
704
  }
466
705
 
467
- /**
468
- * Extractable type class for data structures that can extract their values
469
- * with various fallback strategies.
470
- *
471
- * This interface is implemented by Option, Either, and other types that
472
- * wrap values and need safe extraction methods.
473
- */
474
- interface Extractable<T extends Type> {
475
- /**
476
- * Extracts the value unsafely
477
- * @throws Error if the container is empty
478
- * @returns The contained value
479
- */
480
- get(): T;
481
- /**
482
- * Returns the contained value or a default value
483
- * @param defaultValue - The value to return if extraction fails
484
- * @returns The contained value or defaultValue
485
- */
486
- getOrElse(defaultValue: T): T;
487
- /**
488
- * Returns the contained value or throws an error
489
- * @param error - Optional error to throw (implementations may have defaults)
490
- * @returns The contained value
491
- * @throws The specified error if extraction fails
492
- */
493
- getOrThrow(error?: Error): T;
494
- /**
495
- * Returns this container if it has a value, otherwise returns the alternative
496
- * @param alternative - The alternative container
497
- * @returns This container or the alternative
498
- */
499
- orElse(alternative: Extractable<T>): Extractable<T>;
500
- /**
501
- * Returns the contained value or null
502
- * @returns The contained value or null
503
- */
504
- orNull(): T | null;
505
- /**
506
- * Returns the contained value or undefined
507
- * @returns The contained value or undefined
508
- */
509
- orUndefined(): T | undefined;
510
- }
511
- /**
512
- * Type guard to check if a value implements ExtractableOption
513
- */
514
- declare function isExtractable<T extends Type>(value: unknown): value is Extractable<T>;
515
-
516
706
  /**
517
707
  * Pattern matching interface for functional data types.
518
708
  *
@@ -628,7 +818,7 @@ declare const tryCatchAsync: <L extends Type, R extends Type>(f: () => Promise<R
628
818
  * @module Either
629
819
  * @category Core
630
820
  */
631
- interface Either<L extends Type, R extends Type> extends FunctypeBase<R, "Left" | "Right">, Promisable<R> {
821
+ interface Either<L extends Type, R extends Type> extends FunctypeBase<R, "Left" | "Right">, Promisable<R>, DoProtocol<R>, Reshapeable<R> {
632
822
  readonly _tag: "Left" | "Right";
633
823
  value: L | R;
634
824
  isLeft(): this is Either<L, R> & {
@@ -713,4 +903,4 @@ declare const Either: {
713
903
  fromBinary: <L extends Type, R extends Type>(binary: string) => Either<L, R>;
714
904
  };
715
905
 
716
- export { type Applicative as A, type Collection as C, Either as E, type FunctypeBase as F, List as L, type Matchable as M, None as N, Option as O, type Promisable as P, Right as R, Some as S, type Traversable as T, type Extractable as a, type TestEither as b, Left as c, isLeft as d, TypeCheckRight as e, TypeCheckLeft as f, tryCatchAsync as g, isExtractable as h, isRight as i, type Functype as j, type FunctypeCollection as k, MatchableUtils as l, OptionConstructor as m, Set as n, type CollectionOps as o, type ContainerOps as p, type AsyncMonad as q, type Functor as r, type Monad as s, tryCatch as t };
906
+ export { type Applicative as A, type Collection as C, DO_PROTOCOL as D, Either as E, type FunctypeBase as F, List as L, type Matchable as M, None as N, Option as O, type Promisable as P, Right as R, Some as S, Try as T, type DoResult as a, type Extractable as b, type Traversable as c, type DoProtocol as d, type TestEither as e, Left as f, isLeft as g, TypeCheckRight as h, isRight as i, TypeCheckLeft as j, tryCatchAsync as k, isExtractable as l, type Functype as m, type FunctypeCollection as n, MatchableUtils as o, OptionConstructor as p, Set as q, type TypeNames as r, type CollectionOps as s, tryCatch as t, type ContainerOps as u, type AsyncMonad as v, type Functor as w, type Monad as x, type Reshapeable as y };