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
|
||||
* @typeParam E - The type of the error
|
||||
@ -77,6 +79,50 @@ export const fold =
|
||||
(fa: Either<A, E>): B =>
|
||||
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
|
||||
* @typeParam T - The type of the accumulated scope
|
||||
@ -277,10 +323,9 @@ export class AsyncDoEither<T extends Record<string, unknown>, E> {
|
||||
* @param f - Function returning Either or Promise of Either
|
||||
* @returns A new AsyncDoEither instance with the same scope
|
||||
*/
|
||||
doL(f:
|
||||
(scope: T) =>
|
||||
| Either<unknown, E>
|
||||
| Promise<Either<unknown, E>>): AsyncDoEither<T, E>
|
||||
doL(f: (scope: T) =>
|
||||
| Either<unknown, E>
|
||||
| Promise<Either<unknown, E>>): AsyncDoEither<T, E>
|
||||
{
|
||||
const newPromise = this.promise.then(async (currentEither) => {
|
||||
if (currentEither._tag === 'Left') {
|
||||
|
||||
126
src/option.ts
126
src/option.ts
@ -1,3 +1,5 @@
|
||||
import { Either, left, right } from './either'
|
||||
|
||||
/**
|
||||
* 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> =>
|
||||
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
|
||||
* @typeParam A - The value type
|
||||
@ -155,6 +205,42 @@ export class DoOption<T extends Record<string, unknown>> {
|
||||
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
|
||||
* @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 A promise of the current Option scope
|
||||
|
||||
Loading…
Reference in New Issue
Block a user