Either
Either is an abstraction in F-Box that encapsulates a value that can represent one of two cases: a valid result (Right) or an error/invalid state (Left). It is commonly used to handle computations that can succeed or fail in a type-safe manner.
The Either abstraction simplifies error handling and branching logic. By distinguishing between Right (success) and Left (failure), it enables composability, error propagation, and concise handling of complex scenarios.
Key Features
- Encapsulation: Wraps values to represent success (
Right) or failure (Left). - Error Safety: Propagates errors through computations without manual checks.
- Composability: Provides operators and methods for chaining computations.
- Functional Design: Adheres to functional programming principles with immutability and predictability.
Left and Right
The Either type has two specific forms:
Left<L>
Represents an Either with an invalid result or error. Operations like map, flatMap, and apply propagate the Left instance without executing their logic.
Example
const leftValue = Either.left("Error")console.log(leftValue.getValue()) // Outputs: "Error"Key Characteristics
- Contains an error or invalid value of type
L. - Propagates itself through transformations without invoking their logic.
Right<L, R>
Represents an Either with a valid result. Operations like map, flatMap, and apply execute their logic and produce a new Either.
Example
const rightValue = Either.right(42)console.log(rightValue.getValue()) // Outputs: 42Key Characteristics
- Contains a valid value of type
R. - Executes transformations and returns new
RightorLeftinstances.
Creating an Either
To create an Either, use the static method Either.pack (alias for Either.right). Alternatively, you can use Either.left to create a Left directly.
Example
import { Either } from "f-box-core"
// Create a Rightconst rightValue = Either.pack(42)console.log(rightValue.getValue()) // Outputs: 42
// Create a Leftconst leftValue = Either.left("Error")console.log(leftValue.getValue()) // Outputs: "Error"This demonstrates how to:
- Create a
Rightfor valid results. - Create a
Leftfor errors or invalid states.
Supported Operators
The Either abstraction provides the following operators for working with its encapsulated value. Each operator is an alias for a corresponding method.
<$>: Alias formap. Applies a transformation function to the value in aRight.<*>: Alias forapply. Combines a function in aRightwith a value in anotherRight.>>=: Alias forflatMap. Chains computations that return newEitherinstances.<?>: Alias fororElse. Returns the currentEitherif it is aRight, or a defaultEitherif it is aLeft.<|>: Alias forgetOrElse. Returns the encapsulated value if it is aRight, or a default value if it is aLeft.
For detailed usage, see API Methods.
API Methods
Either.pack
Either.pack<R>(value: R): Right<L, R>
Creates a Right instance containing the given value.
const rightValue = Either.pack(42)console.log(rightValue.getValue()) // Outputs: 42Either.left
Either.left<L>(value: L): Left<L>
Creates a Left instance encapsulating the given value.
const leftValue = Either.left("Error")console.log(leftValue.getValue()) // Outputs: "Error"map
map<U>(fn: (value: R) => U): Either<L, U>
Applies a transformation function to the value inside a Right and returns a new Either. If the instance is a Left, it propagates the Left.
Alias: <$>
const rightValue = Either.right(10)const transformed = rightValue["<$>"]((x) => x * 2)console.log(transformed.getValue()) // Outputs: 20
const leftValue = Either.left("Error")const unchanged = leftValue["<$>"]((x) => x * 2)console.log(unchanged.getValue()) // Outputs: "Error"// Method callconst transformed = rightValue.map((x) => x * 2)console.log(transformed.getValue()) // Outputs: 20apply
apply<A, B>(this: Either<L, (a: A) => B>, boxValue: Either<L, A>): Either<L, B>
Applies a function wrapped in a Right to a value wrapped in another Right, returning a new Right. If either instance is a Left, it propagates the Left.
Alias: <*>
const rightFn = Either.right((x: number) => x + 5)const rightValue = Either.right(10)const result = rightFn["<*>"](rightValue)console.log(result.getValue()) // Outputs: 15
const leftFn = Either.left("Error")const unchanged = leftFn["<*>"](rightValue)console.log(unchanged.getValue()) // Outputs: "Error"// Method callconst result = rightFn.apply(rightValue)console.log(result.getValue()) // Outputs: 15flatMap
flatMap<U>(fn: (value: R) => Either<L, U>): Either<L, U>
Applies a function returning an Either to the value inside a Right and flattens the result. If the instance is a Left, it propagates the Left.
Alias: >>=
const rightValue = Either.right(10)const transformed = rightValue[">>="]((x) => x > 5 ? Either.right(x * 2) : Either.left("Value too small"))console.log(transformed.getValue()) // Outputs: 20// Method callconst transformed = rightValue.flatMap((x) => x > 5 ? Either.right(x * 2) : Either.left("Value too small"))console.log(transformed.getValue()) // Outputs: 20orElse
orElse<U>(defaultValue: Either<L, U>): Either<L, U>
Returns the current Either if it is a Right, or the given default Either if it is a Left.
Alias: <?>
const rightValue = Either.right(10)const result = rightValue["<?>"](Either.right(20))console.log(result.getValue()) // Outputs: 10
const leftValue = Either.left("Error")const fallback = leftValue["<?>"](Either.right(20))console.log(fallback.getValue()) // Outputs: 20// Method callconst result = rightValue.orElse(Either.right(20))console.log(result.getValue()) // Outputs: 10
const fallback = leftValue.orElse(Either.right(20))console.log(fallback.getValue()) // Outputs: 20getOrElse
getOrElse(defaultValue: R): R
Returns the value inside a Right, or the provided default value if it is a Left.
Alias: <|>
const result = rightValue["<|>"](20)console.log(result) // Outputs: 10
const fallback = leftValue["<|>"](20)console.log(fallback) // Outputs: 20// Method callconst result = rightValue.getOrElse(20)console.log(result) // Outputs: 10
const fallback = leftValue.getOrElse(20)console.log(fallback) // Outputs: 20match
match<U>(onLeft: (value: L) => U, onRight: (value: R) => U): U
Matches the current Either instance against the Left or Right cases and applies the corresponding function.
const rightValue = Either.right(42)const result = rightValue.match( (error) => `Error: ${error}`, (value) => `Success: ${value}`)console.log(result) // Outputs: Success: 42
const leftValue = Either.left("Error")const fallback = leftValue.match( (error) => `Error: ${error}`, (value) => `Success: ${value}`)console.log(fallback) // Outputs: Error: ErrorHelper Methods
Either.isLeft
Either.isLeft(value: Either<L, R>): boolean
Checks if a given Either is a Left.
const leftValue = Either.left("Error")console.log(Either.isLeft(leftValue)) // Outputs: true
const rightValue = Either.right(42)console.log(Either.isLeft(rightValue)) // Outputs: falseEither.isRight
Either.isRight(value: Either<L, R>): boolean
Checks if a given Either is a Right.
const rightValue = Either.right(42)console.log(Either.isRight(rightValue)) // Outputs: true
const leftValue = Either.left("Error")console.log(Either.isRight(leftValue)) // Outputs: falseEither.isEither
Either.isEither(value: any): boolean
Checks if a given value is an Either.
const eitherValue = Either.right(42)console.log(Either.isEither(eitherValue)) // Outputs: true
const nonEitherValue = { notEither: true }console.log(Either.isEither(nonEitherValue)) // Outputs: falseUse Cases
Handling Success and Failure
Explicitly handle Right (success) and Left (failure) cases:
const value = Either.right(42)const result = value.match( (error) => `Error: ${error}`, (success) => `Success: ${success}`)console.log(result) // Outputs: Success: 42Chaining Computations
Chain multiple computations, stopping at the first failure:
const f = (x: number): Either<string, number> => x > 5 ? Either.right(x * 2) : Either.left("Too small")const g = (x: number): Either<string, number> => Either.right(x + 5)
const result = Either.right(10) // Start with an initial Right value of 10 [">>="](f) // Apply f, which doubles the value if it's greater than 5 [">>="](g) // Apply g, which increments the value by 5
console.log(result.getValue()) // Outputs: 25Providing Fallbacks
Provide fallback values or computations:
const leftValue = Either.left("Error")const fallback = leftValue["<?>"](Either.right(42))console.log(fallback.getValue()) // Outputs: 42Why Use Either?
The Either abstraction offers a robust framework for handling computations that can succeed or fail. By leveraging Right and Left to represent valid and invalid results, it enables predictable, composable, and error-safe programming.
Next Steps
Explore other abstractions in F-Box: