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: 42
Key Characteristics
- Contains a valid value of type
R
. - Executes transformations and returns new
Right
orLeft
instances.
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
Right
for valid results. - Create a
Left
for 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 aRight
with a value in anotherRight
.>>=
: Alias forflatMap
. Chains computations that return newEither
instances.<?>
: Alias fororElse
. Returns the currentEither
if it is aRight
, or a defaultEither
if 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: 42
Either.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: 20
apply
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: 15
flatMap
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: 20
orElse
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: 20
getOrElse
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: 20
match
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: Error
Helper 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: false
Either.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: false
Either.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: false
Use 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: 42
Chaining 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: 25
Providing Fallbacks
Provide fallback values or computations:
const leftValue = Either.left("Error")const fallback = leftValue["<?>"](Either.right(42))console.log(fallback.getValue()) // Outputs: 42
Why 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: