/** * @since 1.0.0 */ import type * as Data from "@effect/data/Data" import * as Equivalence from "@effect/data/Equivalence" import type { LazyArg } from "@effect/data/Function" import { constNull, constUndefined, dual, identity } from "@effect/data/Function" import type { TypeLambda } from "@effect/data/HKT" import type { Inspectable } from "@effect/data/Inspectable" import * as either from "@effect/data/internal/Either" import type { Option } from "@effect/data/Option" import type { Pipeable } from "@effect/data/Pipeable" import { isFunction } from "@effect/data/Predicate" import type * as Unify from "@effect/data/Unify" import * as Gen from "@effect/data/UtilsGen" /** * @category models * @since 1.0.0 */ export type Either = Left | Right /** * @category symbols * @since 1.0.0 */ export const TypeId: unique symbol = either.TypeId /** * @category symbols * @since 1.0.0 */ export type TypeId = typeof TypeId /** * @category models * @since 1.0.0 */ export interface Left extends Data.Case, Pipeable, Inspectable { readonly _tag: "Left" readonly left: E readonly [TypeId]: { readonly _A: (_: never) => A readonly _E: (_: never) => E } [Unify.typeSymbol]?: unknown [Unify.unifySymbol]?: EitherUnify [Unify.blacklistSymbol]?: EitherUnifyBlacklist } /** * @category models * @since 1.0.0 */ export interface Right extends Data.Case, Pipeable, Inspectable { readonly _tag: "Right" readonly right: A readonly [TypeId]: { readonly _A: (_: never) => A readonly _E: (_: never) => E } [Unify.typeSymbol]?: unknown [Unify.unifySymbol]?: EitherUnify [Unify.blacklistSymbol]?: EitherUnifyBlacklist } /** * @category models * @since 1.0.0 */ export interface EitherUnify { Either?: () => A[Unify.typeSymbol] extends Either | infer _ ? Either : never } /** * @category models * @since 1.0.0 */ export interface EitherUnifyBlacklist {} /** * @category type lambdas * @since 1.0.0 */ export interface EitherTypeLambda extends TypeLambda { readonly type: Either } /** * Constructs a new `Either` holding a `Right` value. This usually represents a successful value due to the right bias * of this structure. * * @category constructors * @since 1.0.0 */ export const right: (a: A) => Either = either.right /** * Constructs a new `Either` holding a `Left` value. This usually represents a failure, due to the right-bias of this * structure. * * @category constructors * @since 1.0.0 */ export const left: (e: E) => Either = either.left /** * Takes a lazy default and a nullable value, if the value is not nully (`null` or `undefined`), turn it into a `Right`, if the value is nully use * the provided default as a `Left`. * * @example * import * as Either from '@effect/data/Either' * * assert.deepStrictEqual(Either.fromNullable(1, () => 'fallback'), Either.right(1)) * assert.deepStrictEqual(Either.fromNullable(null, () => 'fallback'), Either.left('fallback')) * * @category constructors * @since 1.0.0 */ export const fromNullable: { (onNullable: (a: A) => E): (self: A) => Either> (self: A, onNullable: (a: A) => E): Either> } = dual( 2, (self: A, onNullable: (a: A) => E): Either> => self == null ? left(onNullable(self)) : right(self as NonNullable) ) /** * @example * import * as Either from '@effect/data/Either' * import * as Option from '@effect/data/Option' * * assert.deepStrictEqual(Either.fromOption(Option.some(1), () => 'error'), Either.right(1)) * assert.deepStrictEqual(Either.fromOption(Option.none(), () => 'error'), Either.left('error')) * * @category constructors * @since 1.0.0 */ export const fromOption: { (self: Option, onNone: () => E): Either (onNone: () => E): (self: Option) => Either } = either.fromOption const try_: { ( options: { readonly try: LazyArg; readonly catch: (error: unknown) => E } ): Either (evaluate: LazyArg): Either } = (( evaluate: LazyArg | { readonly try: LazyArg; readonly catch: (error: unknown) => E } ) => { if (isFunction(evaluate)) { try { return right(evaluate()) } catch (e) { return left(e) } } else { try { return right(evaluate.try()) } catch (e) { return left(evaluate.catch(e)) } } }) as any export { /** * Imports a synchronous side-effect into a pure `Either` value, translating any * thrown exceptions into typed failed eithers creating with `Either.left`. * * @category constructors * @since 1.0.0 */ try_ as try } /** * Tests if a value is a `Either`. * * @param input - The value to test. * * @example * import { isEither, left, right } from '@effect/data/Either' * * assert.deepStrictEqual(isEither(right(1)), true) * assert.deepStrictEqual(isEither(left("a")), true) * assert.deepStrictEqual(isEither({ right: 1 }), false) * * @category guards * @since 1.0.0 */ export const isEither: (input: unknown) => input is Either = either.isEither /** * Determine if a `Either` is a `Left`. * * @param self - The `Either` to check. * * @example * import { isLeft, left, right } from '@effect/data/Either' * * assert.deepStrictEqual(isLeft(right(1)), false) * assert.deepStrictEqual(isLeft(left("a")), true) * * @category guards * @since 1.0.0 */ export const isLeft: (self: Either) => self is Left = either.isLeft /** * Determine if a `Either` is a `Right`. * * @param self - The `Either` to check. * * @example * import { isRight, left, right } from '@effect/data/Either' * * assert.deepStrictEqual(isRight(right(1)), true) * assert.deepStrictEqual(isRight(left("a")), false) * * @category guards * @since 1.0.0 */ export const isRight: (self: Either) => self is Right = either.isRight /** * Converts a `Either` to an `Option` discarding the `Left`. * * Alias of {@link toOption}. * * @example * import * as O from '@effect/data/Option' * import * as E from '@effect/data/Either' * * assert.deepStrictEqual(E.getRight(E.right('ok')), O.some('ok')) * assert.deepStrictEqual(E.getRight(E.left('err')), O.none()) * * @category getters * @since 1.0.0 */ export const getRight: (self: Either) => Option = either.getRight /** * Converts a `Either` to an `Option` discarding the value. * * @example * import * as O from '@effect/data/Option' * import * as E from '@effect/data/Either' * * assert.deepStrictEqual(E.getLeft(E.right('ok')), O.none()) * assert.deepStrictEqual(E.getLeft(E.left('err')), O.some('err')) * * @category getters * @since 1.0.0 */ export const getLeft: (self: Either) => Option = either.getLeft /** * @category equivalence * @since 1.0.0 */ export const getEquivalence = ( EE: Equivalence.Equivalence, EA: Equivalence.Equivalence ): Equivalence.Equivalence> => Equivalence.make((x, y) => x === y || (isLeft(x) ? isLeft(y) && EE(x.left, y.left) : isRight(y) && EA(x.right, y.right)) ) /** * @category mapping * @since 1.0.0 */ export const mapBoth: { (options: { readonly onLeft: (e: E1) => E2 readonly onRight: (a: A) => B }): (self: Either) => Either (self: Either, options: { readonly onLeft: (e: E1) => E2 readonly onRight: (a: A) => B }): Either } = dual( 2, (self: Either, { onLeft, onRight }: { readonly onLeft: (e: E1) => E2 readonly onRight: (a: A) => B }): Either => isLeft(self) ? left(onLeft(self.left)) : right(onRight(self.right)) ) /** * Maps the `Left` side of an `Either` value to a new `Either` value. * * @param self - The input `Either` value to map. * @param f - A transformation function to apply to the `Left` value of the input `Either`. * * @category mapping * @since 1.0.0 */ export const mapLeft: { (f: (e: E) => G): (self: Either) => Either (self: Either, f: (e: E) => G): Either } = dual( 2, (self: Either, f: (e: E) => G): Either => isLeft(self) ? left(f(self.left)) : right(self.right) ) /** * Maps the `Right` side of an `Either` value to a new `Either` value. * * @param self - An `Either` to map * @param f - The function to map over the value of the `Either` * * @category mapping * @since 1.0.0 */ export const map: { (f: (a: A) => B): (self: Either) => Either (self: Either, f: (a: A) => B): Either } = dual( 2, (self: Either, f: (a: A) => B): Either => isRight(self) ? right(f(self.right)) : left(self.left) ) /** * Takes two functions and an `Either` value, if the value is a `Left` the inner value is applied to the `onLeft function, * if the value is a `Right` the inner value is applied to the `onRight` function. * * @example * import * as E from '@effect/data/Either' * import { pipe } from '@effect/data/Function' * * const onLeft = (strings: ReadonlyArray): string => `strings: ${strings.join(', ')}` * * const onRight = (value: number): string => `Ok: ${value}` * * assert.deepStrictEqual(pipe(E.right(1), E.match({ onLeft, onRight })), 'Ok: 1') * assert.deepStrictEqual( * pipe(E.left(['string 1', 'string 2']), E.match({ onLeft, onRight })), * 'strings: string 1, string 2' * ) * * @category pattern matching * @since 1.0.0 */ export const match: { (options: { readonly onLeft: (e: E) => B readonly onRight: (a: A) => C }): (self: Either) => B | C (self: Either, options: { readonly onLeft: (e: E) => B readonly onRight: (a: A) => C }): B | C } = dual( 2, (self: Either, { onLeft, onRight }: { readonly onLeft: (e: E) => B readonly onRight: (a: A) => C }): B | C => isLeft(self) ? onLeft(self.left) : onRight(self.right) ) /** * @category getters * @since 1.0.0 */ export const merge: (self: Either) => E | A = match({ onLeft: identity, onRight: identity }) /** * Returns the wrapped value if it's a `Right` or a default value if is a `Left`. * * @example * import * as Either from '@effect/data/Either' * * assert.deepStrictEqual(Either.getOrElse(Either.right(1), (error) => error + "!"), 1) * assert.deepStrictEqual(Either.getOrElse(Either.left("not a number"), (error) => error + "!"), "not a number!") * * @category getters * @since 1.0.0 */ export const getOrElse: { (onLeft: (e: E) => B): (self: Either) => B | A (self: Either, onLeft: (e: E) => B): A | B } = dual( 2, (self: Either, onLeft: (e: E) => B): A | B => isLeft(self) ? onLeft(self.left) : self.right ) /** * @example * import * as Either from '@effect/data/Either' * * assert.deepStrictEqual(Either.getOrNull(Either.right(1)), 1) * assert.deepStrictEqual(Either.getOrNull(Either.left("a")), null) * * @category getters * @since 1.0.0 */ export const getOrNull: (self: Either) => A | null = getOrElse(constNull) /** * @example * import * as Either from '@effect/data/Either' * * assert.deepStrictEqual(Either.getOrUndefined(Either.right(1)), 1) * assert.deepStrictEqual(Either.getOrUndefined(Either.left("a")), undefined) * * @category getters * @since 1.0.0 */ export const getOrUndefined: (self: Either) => A | undefined = getOrElse(constUndefined) /** * Extracts the value of an `Either` or throws if the `Either` is `Left`. * * If a default error is sufficient for your use case and you don't need to configure the thrown error, see {@link getOrThrow}. * * @param self - The `Either` to extract the value from. * @param onLeft - A function that will be called if the `Either` is `Left`. It returns the error to be thrown. * * @example * import * as E from "@effect/data/Either" * * assert.deepStrictEqual( * E.getOrThrowWith(E.right(1), () => new Error('Unexpected Left')), * 1 * ) * assert.throws(() => E.getOrThrowWith(E.left("error"), () => new Error('Unexpected Left'))) * * @category getters * @since 1.0.0 */ export const getOrThrowWith: { (onLeft: (e: E) => unknown): (self: Either) => A (self: Either, onLeft: (e: E) => unknown): A } = dual(2, (self: Either, onLeft: (e: E) => unknown): A => { if (isRight(self)) { return self.right } throw onLeft(self.left) }) /** * Extracts the value of an `Either` or throws if the `Either` is `Left`. * * The thrown error is a default error. To configure the error thrown, see {@link getOrThrowWith}. * * @param self - The `Either` to extract the value from. * @throws `Error("getOrThrow called on a Left")` * * @example * import * as E from "@effect/data/Either" * * assert.deepStrictEqual(E.getOrThrow(E.right(1)), 1) * assert.throws(() => E.getOrThrow(E.left("error"))) * * @category getters * @since 1.0.0 */ export const getOrThrow: (self: Either) => A = getOrThrowWith(() => new Error("getOrThrow called on a Left") ) /** * Returns `self` if it is a `Right` or `that` otherwise. * * @param self - The input `Either` value to check and potentially return. * @param that - A function that takes the error value from `self` (if it's a `Left`) and returns a new `Either` value. * * @category error handling * @since 1.0.0 */ export const orElse: { (that: (e1: E1) => Either): (self: Either) => Either (self: Either, that: (e1: E1) => Either): Either } = dual( 2, (self: Either, that: (e1: E1) => Either): Either => isLeft(self) ? that(self.left) : right(self.right) ) /** * @category combining * @since 1.0.0 */ export const flatMap: { (f: (a: A) => Either): (self: Either) => Either (self: Either, f: (a: A) => Either): Either } = dual( 2, (self: Either, f: (a: A) => Either): Either => isLeft(self) ? left(self.left) : f(self.right) ) /** * Takes a structure of `Option`s and returns an `Option` of values with the same structure. * * - If a tuple is supplied, then the returned `Option` will contain a tuple with the same length. * - If a struct is supplied, then the returned `Option` will contain a struct with the same keys. * - If an iterable is supplied, then the returned `Option` will contain an array. * * @param fields - the struct of `Option`s to be sequenced. * * @example * import * as Either from "@effect/data/Either" * * assert.deepStrictEqual(Either.all([Either.right(1), Either.right(2)]), Either.right([1, 2])) * assert.deepStrictEqual(Either.all({ a: Either.right(1), b: Either.right("hello") }), Either.right({ a: 1, b: "hello" })) * assert.deepStrictEqual(Either.all({ a: Either.right(1), b: Either.left("error") }), Either.left("error")) * * @category combining * @since 1.0.0 */ // @ts-expect-error export const all: > | Record>>( input: I ) => [I] extends [ReadonlyArray>] ? Either< I[number] extends never ? never : [I[number]] extends [Either] ? E : never, { -readonly [K in keyof I]: [I[K]] extends [Either] ? A : never } > : [I] extends [Iterable>] ? Either> : Either< I[keyof I] extends never ? never : [I[keyof I]] extends [Either] ? E : never, { -readonly [K in keyof I]: [I[K]] extends [Either] ? A : never } > = ( input: Iterable> | Record> ): Either => { if (Symbol.iterator in input) { const out: Array> = [] for (const e of (input as Iterable>)) { if (isLeft(e)) { return e } out.push(e.right) } return right(out) } const out: Record = {} for (const key of Object.keys(input)) { const e = input[key] if (isLeft(e)) { return e } out[key] = e.right } return right(out) } /** * @since 1.0.0 */ export const reverse = (self: Either): Either => isLeft(self) ? right(self.left) : left(self.right) const adapter = Gen.adapter() /** * @category generators * @since 1.0.0 */ export const gen: Gen.Gen> = (f) => { const iterator = f(adapter) let state: IteratorYieldResult | IteratorReturnResult = iterator.next() if (state.done) { return right(void 0) as any } else { let current = state.value.value if (isLeft(current)) { return current } while (!state.done) { state = iterator.next(current.right) if (!state.done) { current = state.value.value if (isLeft(current)) { return current } } } return right(state.value) } }