Overview
The Either<R, L> type represents a value that can be one of two types: a Right (success) value of type R, or a Left (error) value of type L. In Parserator, Either is used extensively to represent parser results, where Right contains the successfully parsed value and Left contains error information.
import { Either, Left, Right } from 'parserator';
Type Definition
type Either<R, L> = Left<L, R> | Right<R, L>
An Either is a discriminated union of Left and Right classes, both of which have a _tag property for type discrimination.
Classes
Left
Represents a failure or error case.
class Left<L, R = never> {
readonly _tag = "Left";
constructor(public readonly left: L);
}
Discriminator property for type checking
The error or failure value
Right
Represents a success case.
class Right<R, L = any> {
readonly _tag = "Right";
constructor(public readonly right: R);
}
Discriminator property for type checking
Static Methods
Either.left
Creates a Left (error) value.
static left<L, R = never>(l: L): Either<R, L>
An Either in the Left state
const error = Either.left(new Error("Parse failed"));
// Either<never, Error>
Either.right
Creates a Right (success) value.
static right<R, L = never>(r: R): Either<R, L>
The success value to wrap
An Either in the Right state
const success = Either.right(42);
// Either<number, never>
Either.right uses pre-allocated instances for common values (undefined, true, false, "") for better performance.
Either.isLeft
Type guard to check if an Either is a Left (error).
static isLeft<R, L>(either: Either<R, L>): either is Left<L, R>
True if the Either is a Left, false otherwise
const result = parseNumber("abc");
if (Either.isLeft(result)) {
console.error("Parse failed:", result.left);
}
Either.isRight
Type guard to check if an Either is a Right (success).
static isRight<R, L>(either: Either<R, L>): either is Right<R, L>
True if the Either is a Right, false otherwise
const result = parseNumber("42");
if (Either.isRight(result)) {
console.log("Parsed value:", result.right);
}
Either.match
Pattern matches on an Either, applying different functions for Left and Right cases.
static match<R, L, T>(
onLeft: (left: L) => T,
onRight: (right: R) => T
): (either: Either<R, L>) => T
Function to call if the Either is a Left
Function to call if the Either is a Right
matcher
(either: Either<R, L>) => T
A function that takes an Either and returns the result of applying the appropriate function
const result = parseNumber("42");
const message = Either.match(
error => `Error: ${error}`,
value => `Success: ${value}`
)(result);
// "Success: 42"
Either.gen
Creates an Either from a generator function, enabling imperative-style composition with early returns on Left values.
static gen<R, L>(
f: () => Generator<Either<any, L>, R, any>
): Either<R, L>
f
() => Generator<Either<any, L>, R, any>
required
A generator function that yields Either values and returns the final result
An Either containing the final result or the first Left encountered
const result = Either.gen(function* () {
const a = yield* Either.right(10);
const b = yield* Either.right(20);
const c = yield* Either.right(12);
return a + b + c;
});
// Either.right(42)
const error = Either.gen(function* () {
const a = yield* Either.right(10);
const b = yield* Either.left("error");
const c = yield* Either.right(12); // Never reached
return a + b + c;
});
// Either.left("error")
Usage in Parser Results
In Parserator, Either is used to represent parser results:
Either<T, ParseErrorBundle> - A successful parse of type T or a bundle of parse errors
Right<T> - Successful parse containing the value
Left<ParseErrorBundle> - Failed parse containing error information
const parser = number();
const output = parser.parse("42");
if (output.result._tag === "Right") {
console.log("Parsed:", output.result.right); // 42
} else {
console.error("Error:", output.result.left.format());
}
Pattern Matching Examples
Basic Pattern Matching
const result: Either<number, string> = Either.right(42);
// Using match
const message = Either.match(
error => `Failed: ${error}`,
value => `Success: ${value}`
)(result);
// Using type guards
if (Either.isRight(result)) {
console.log(result.right); // TypeScript knows this is number
} else {
console.error(result.left); // TypeScript knows this is string
}
Using Tag Discrimination
const result: Either<number, Error> = parseNumber(input);
if (result._tag === "Right") {
// TypeScript narrows to Right<number, Error>
const value: number = result.right;
console.log("Parsed:", value);
} else {
// TypeScript narrows to Left<Error, number>
const error: Error = result.left;
console.error("Parse failed:", error.message);
}
Generator-Based Composition
// Chain multiple Either operations
const processData = Either.gen(function* () {
const raw = yield* readFile("data.txt");
const parsed = yield* parseJSON(raw);
const validated = yield* validateSchema(parsed);
return transformed(validated);
});
// If any step returns Left, execution stops and returns that error
// If all succeed, returns Right with the final value
Working with Parser Results
const parser = parser(function* () {
const name = yield* identifier;
yield* char(':');
const value = yield* number;
return { name, value };
});
const output = parser.parse("count:42");
// Pattern match on the result
const result = Either.match(
errorBundle => {
console.error(errorBundle.format());
return null;
},
value => {
console.log(`${value.name} = ${value.value}`);
return value;
}
)(output.result);
The Either implementation includes several performance optimizations:
- Pre-allocated instances: Common
Right values (undefined, true, false, "", []) are pre-allocated to avoid repeated allocations
- Lightweight classes: Both
Left and Right are simple classes with minimal overhead
- Direct property access: The
_tag discriminator allows for fast type checking without function calls
// These all use pre-allocated instances
Either.right(undefined);
Either.right(true);
Either.right(false);
Either.right("");
// Custom values create new instances
Either.right(42);
Either.right("hello");