Functional Programming Types in TypeScript
This challenge focuses on implementing core functional programming data types in TypeScript. Functional programming emphasizes immutability and pure functions, and these data types are fundamental building blocks for such paradigms. Successfully completing this challenge will demonstrate your understanding of TypeScript's type system and its application to functional programming concepts.
Problem Description
You are tasked with implementing the following functional programming types in TypeScript:
Maybe<T>(orOptional<T>): Represents a value that may or may not be present. It's a safer alternative tonullorundefined, forcing you to explicitly handle the absence of a value. It should be parameterized by a typeT.Either<L, R>: Represents a value that can be either a "Left" value of typeL(typically representing an error) or a "Right" value of typeR(representing success). This is useful for handling errors in a functional way, avoiding exceptions. It should be parameterized by two typesLandR.Validated<E, A>: Represents a value of typeAthat has been validated and may have associated errors of typeE. It's similar toEither, but specifically designed for validation scenarios where you might have multiple errors. It should be parameterized by two typesEandA.
For each type, you need to define the TypeScript type and provide a set of basic utility functions to work with them. These functions should be type-safe and demonstrate the intended usage of each type.
Key Requirements:
- Type Definitions: Accurate and concise type definitions for
Maybe,Either, andValidated. - Utility Functions: Implement the following functions for each type:
Maybe:map<U>(f: (a: T) => U): Maybe<U>- Applies a function to the value if it exists.Either:map<R>(f: (r: R) => R): Either<L, R>- Applies a function to the right value if it exists.Either:flatMap<R>(f: (l: L) => Either<L, R>): Either<L, R>- Applies a function that returns anEitherto the left value.Validated:map<U>(f: (a: A) => U): Validated<E, U>- Applies a function to the value if it's valid.Validated:reduce<R>(f: (e: E, acc: R) => R, initial: R): R- Reduces the errors into a single value.
Examples
Example 1: Maybe
Input:
const maybeValue: Maybe<number> = { value: 10 };
const emptyMaybe: Maybe<number> = { hasValue: false };
Output:
const mappedValue: Maybe<string> = maybeValue.map(x => x.toString()); // { value: "10" }
const emptyMappedValue: Maybe<string> = emptyMaybe.map(x => x.toString()); // { hasValue: false }
Explanation: The map function applies the provided function to the value inside the Maybe only if a value exists. If the Maybe is empty, the map function returns an empty Maybe.
Example 2: Either
Input:
const rightEither: Either<string, number> = { right: 42 };
const leftEither: Either<string, number> = { left: "Error message" };
Output:
const mappedRight: Either<string, string> = rightEither.map(x => x.toString()); // { right: "42" }
const flatMappedLeft: Either<string, number> = leftEither.flatMap(err => {
if (err === "Error message") {
return { left: "New Error" };
}
return { right: 0 };
}); // { left: "New Error" }
Explanation: map applies a function to the right value. flatMap applies a function that returns an Either to the left value.
Example 3: Validated
Input:
const validValidated: Validated<string[], number> = { value: 123, errors: [] };
const invalidValidated: Validated<string[], number> = { value: 456, errors: ["Invalid value"] };
Output:
const mappedValidated: Validated<string[], string> = validValidated.map(x => x.toString()); // { value: "123", errors: [] }
const reducedErrors: string = invalidValidated.reduce((acc, err) => acc + err, ""); // "Invalid value"
Explanation: map applies a function to the value if it's valid. reduce aggregates the errors into a single string.
Constraints
- Type Safety: All functions must be type-safe and adhere to TypeScript's type checking.
- Immutability: The utility functions should not mutate the original
Maybe,Either, orValidatedobjects. They should return new instances. - No External Libraries: You are not allowed to use any external libraries. Implement everything from scratch.
- Performance: While not a primary concern, avoid unnecessarily complex or inefficient implementations.
Notes
- Consider using discriminated unions to represent the different states of
Maybe,Either, andValidated. - Think about how to handle the different cases (presence/absence of value, left/right value, valid/invalid value) within each utility function.
- Focus on clarity and readability in your code. Well-documented code is a plus.
- The
Validatedtype is a more advanced concept. If you find it challenging, prioritize implementingMaybeandEithercorrectly first. - The
flatMapfunction forEitheris a powerful tool for chaining operations that can potentially fail.