Add Option fold and methods/functions for Option/Either conversion
This commit is contained in:
parent
a23edf3605
commit
b71d39c845
@ -1,3 +1,5 @@
|
|||||||
|
import { Option } from './option'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the failure case of an Either
|
* Represents the failure case of an Either
|
||||||
* @typeParam E - The type of the error
|
* @typeParam E - The type of the error
|
||||||
@ -77,6 +79,50 @@ export const fold =
|
|||||||
(fa: Either<A, E>): B =>
|
(fa: Either<A, E>): B =>
|
||||||
fa._tag === 'Left' ? onLeft(fa.left) : onRight(fa.right)
|
fa._tag === 'Left' ? onLeft(fa.left) : onRight(fa.right)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an Option to an Either, providing a default error for None.
|
||||||
|
* @typeParam A - Value type
|
||||||
|
* @typeParam E - Error type
|
||||||
|
* @param onNone - Error value for None case
|
||||||
|
* @returns Function that converts Option<A> to Either<A, E>
|
||||||
|
*/
|
||||||
|
export const fromOption =
|
||||||
|
<E>(onNone: E) => <A>(option: Option<A>): Either<A, E> =>
|
||||||
|
option._tag === 'Some' ? right(option.value) : left(onNone)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazily converts an Option to an Either (error evaluated only when needed).
|
||||||
|
* @typeParam A - Value type
|
||||||
|
* @typeParam E - Error type
|
||||||
|
* @param onNone - Function returning error for None case
|
||||||
|
* @returns Function that converts Option<A> to Either<A, E>
|
||||||
|
*/
|
||||||
|
export const fromOptionL =
|
||||||
|
<E>(onNone: () => E) => <A>(option: Option<A>): Either<A, E> =>
|
||||||
|
option._tag === 'Some' ? right(option.value) : left(onNone())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a Promise<Option> to Promise<Either> with a fixed error.
|
||||||
|
* @typeParam A - Value type
|
||||||
|
* @typeParam E - Error type
|
||||||
|
* @param onNone - Error value for None
|
||||||
|
* @returns Function that converts Promise<Option<A>> to Promise<Either<A, E>>
|
||||||
|
*/
|
||||||
|
export const fromPromiseOption =
|
||||||
|
<E>(onNone: E) => <A>(p: Promise<Option<A>>): Promise<Either<A, E>> =>
|
||||||
|
p.then(option => fromOption(onNone)(option))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazily converts a Promise<Option> to Promise<Either>.
|
||||||
|
* @typeParam A - Value type
|
||||||
|
* @typeParam E - Error type
|
||||||
|
* @param onNone - Function returning error for None
|
||||||
|
* @returns Function that converts Promise<Option<A>> to Promise<Either<A, E>>
|
||||||
|
*/
|
||||||
|
export const fromPromiseOptionL =
|
||||||
|
<E>(onNone: () => E) => <A>(p: Promise<Option<A>>): Promise<Either<A, E>> =>
|
||||||
|
p.then(option => fromOptionL(onNone)(option))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class for working with Either in a do-notation style
|
* A class for working with Either in a do-notation style
|
||||||
* @typeParam T - The type of the accumulated scope
|
* @typeParam T - The type of the accumulated scope
|
||||||
@ -277,8 +323,7 @@ export class AsyncDoEither<T extends Record<string, unknown>, E> {
|
|||||||
* @param f - Function returning Either or Promise of Either
|
* @param f - Function returning Either or Promise of Either
|
||||||
* @returns A new AsyncDoEither instance with the same scope
|
* @returns A new AsyncDoEither instance with the same scope
|
||||||
*/
|
*/
|
||||||
doL(f:
|
doL(f: (scope: T) =>
|
||||||
(scope: T) =>
|
|
||||||
| Either<unknown, E>
|
| Either<unknown, E>
|
||||||
| Promise<Either<unknown, E>>): AsyncDoEither<T, E>
|
| Promise<Either<unknown, E>>): AsyncDoEither<T, E>
|
||||||
{
|
{
|
||||||
|
|||||||
126
src/option.ts
126
src/option.ts
@ -1,3 +1,5 @@
|
|||||||
|
import { Either, left, right } from './either'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the absence of a value
|
* Represents the absence of a value
|
||||||
*/
|
*/
|
||||||
@ -52,6 +54,54 @@ export const chain =
|
|||||||
<A, B>(f: (a: A) => Option<B>) => (fa: Option<A>): Option<B> =>
|
<A, B>(f: (a: A) => Option<B>) => (fa: Option<A>): Option<B> =>
|
||||||
fa._tag === 'Some' ? f(fa.value) : none
|
fa._tag === 'Some' ? f(fa.value) : none
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Folds an Option into a single value by providing handlers for both cases (Some/None)
|
||||||
|
* @typeParam A - The value type contained in the Option
|
||||||
|
* @typeParam B - The output type of the fold operation
|
||||||
|
* @param onNone - Function to handle the None case
|
||||||
|
* @param onSome - Function to handle the Some case
|
||||||
|
* @returns A function that takes an Option and returns the folded value
|
||||||
|
* @example
|
||||||
|
* const result = fold(
|
||||||
|
* () => 'No value',
|
||||||
|
* (value: number) => `Value: ${value}`
|
||||||
|
* )(some(42)) // Returns "Value: 42"
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const result = fold(
|
||||||
|
* () => 'No value',
|
||||||
|
* (value: number) => `Value: ${value}`
|
||||||
|
* )(none) // Returns "No value"
|
||||||
|
*/
|
||||||
|
export const fold =
|
||||||
|
<A, B>(onNone: () => B, onSome: (value: A) => B) => (option: Option<A>): B =>
|
||||||
|
option._tag === 'Some' ? onSome(option.value) : onNone()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A curried version of fold that takes the Option first
|
||||||
|
* @typeParam A - The value type contained in the Option
|
||||||
|
* @typeParam B - The output type of the fold operation
|
||||||
|
* @param option - The Option to fold
|
||||||
|
* @returns An object with methods to handle both cases
|
||||||
|
* @example
|
||||||
|
* some(42).fold({
|
||||||
|
* onNone: () => 'No value',
|
||||||
|
* onSome: (value) => `Value: ${value}`
|
||||||
|
* }) // Returns "Value: 42"
|
||||||
|
*/
|
||||||
|
export const foldC = <A>(option: Option<A>) => ({
|
||||||
|
/**
|
||||||
|
* @param handlers - Object containing both case handlers
|
||||||
|
* @param handlers.onNone - Function to handle None case
|
||||||
|
* @param handlers.onSome - Function to handle Some case
|
||||||
|
* @returns The folded value
|
||||||
|
*/
|
||||||
|
fold: <B>(handlers: {onNone: () => B, onSome: (value: A) => B}): B =>
|
||||||
|
option._tag === 'Some'
|
||||||
|
? handlers.onSome(option.value)
|
||||||
|
: handlers.onNone(),
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the value from an Option or returns a default
|
* Extracts the value from an Option or returns a default
|
||||||
* @typeParam A - The value type
|
* @typeParam A - The value type
|
||||||
@ -155,6 +205,42 @@ export class DoOption<T extends Record<string, unknown>> {
|
|||||||
return this.option
|
return this.option
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the DoOption to an Either, providing a default error value for the None case
|
||||||
|
* @typeParam E - The type of the error to use if the Option is None
|
||||||
|
* @param onNone - The error value to use if the Option is None
|
||||||
|
* @returns An Either - Right with the accumulated scope if the Option is Some,
|
||||||
|
* or Left with the provided error if the Option is None
|
||||||
|
* @example
|
||||||
|
* DoOption.start()
|
||||||
|
* .bind('id', some(1))
|
||||||
|
* .toEither('Missing value')
|
||||||
|
* // Returns Right({ id: 1 }) or Left('Missing value')
|
||||||
|
*/
|
||||||
|
toEither<E>(onNone: E): Either<T, E> {
|
||||||
|
return this.option._tag === 'Some'
|
||||||
|
? right(this.option.value)
|
||||||
|
: left(onNone)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the DoOption to an Either, lazily providing an error value for the None case
|
||||||
|
* @typeParam E - The type of the error to use if the Option is None
|
||||||
|
* @param onNone - A function that returns the error value when Option is None
|
||||||
|
* @returns An Either - Right with the accumulated scope if the Option is Some,
|
||||||
|
* or Left with the computed error if the Option is None
|
||||||
|
* @example
|
||||||
|
* DoOption.start()
|
||||||
|
* .bind('config', loadConfig())
|
||||||
|
* .toEitherL(() => new Error('Config missing'))
|
||||||
|
* // Returns Right(config) or Left(Error)
|
||||||
|
*/
|
||||||
|
toEitherL<E>(onNone: () => E): Either<T, E> {
|
||||||
|
return this.option._tag === 'Some'
|
||||||
|
? right(this.option.value)
|
||||||
|
: left(onNone())
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes an effect without modifying the scope
|
* Executes an effect without modifying the scope
|
||||||
* @param fa - The Option effect to execute (does not depend on scope)
|
* @param fa - The Option effect to execute (does not depend on scope)
|
||||||
@ -303,6 +389,46 @@ export class AsyncDoOption<T extends Record<string, unknown>> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the AsyncDoOption to a Promise of Either, providing a default error value for the None case
|
||||||
|
* @typeParam E - The type of the error to use if the Option is None
|
||||||
|
* @param onNone - The error value to use if the Option is None
|
||||||
|
* @returns A Promise that resolves to an Either - Right with the accumulated scope if the Option is Some,
|
||||||
|
* or Left with the provided error if the Option is None
|
||||||
|
* @example
|
||||||
|
* AsyncDoOption.start()
|
||||||
|
* .bind('user', getUserAsync())
|
||||||
|
* .toEither('User not found')
|
||||||
|
* .then(either => { ... }) // either is Either<{ user: User }, string>
|
||||||
|
*/
|
||||||
|
toEither<E>(onNone: E): Promise<Either<T, E>> {
|
||||||
|
return this.promise.then(option =>
|
||||||
|
option._tag === 'Some'
|
||||||
|
? right(option.value)
|
||||||
|
: left(onNone)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the AsyncDoOption to a Promise of Either, lazily providing an error value for the None case
|
||||||
|
* @typeParam E - The type of the error to use if the Option is None
|
||||||
|
* @param onNone - A function that returns the error value to use if the Option is None
|
||||||
|
* @returns A Promise that resolves to an Either - Right with the accumulated scope if the Option is Some,
|
||||||
|
* or Left with the error from onNone if the Option is None
|
||||||
|
* @example
|
||||||
|
* AsyncDoOption.start()
|
||||||
|
* .bind('data', fetchData())
|
||||||
|
* .toEitherL(() => new Error('Data fetch failed'))
|
||||||
|
* .then(either => { ... }) // either is Either<{ data: Data }, Error>
|
||||||
|
*/
|
||||||
|
toEitherL<E>(onNone: () => E): Promise<Either<T, E>> {
|
||||||
|
return this.promise.then(option =>
|
||||||
|
option._tag === 'Some'
|
||||||
|
? right(option.value)
|
||||||
|
: left(onNone())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current scope
|
* Returns the current scope
|
||||||
* @returns A promise of the current Option scope
|
* @returns A promise of the current Option scope
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user