Hone logo
Hone
Problems

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:

  1. Maybe<T> (or Optional<T>): Represents a value that may or may not be present. It's a safer alternative to null or undefined, forcing you to explicitly handle the absence of a value. It should be parameterized by a type T.
  2. Either<L, R>: Represents a value that can be either a "Left" value of type L (typically representing an error) or a "Right" value of type R (representing success). This is useful for handling errors in a functional way, avoiding exceptions. It should be parameterized by two types L and R.
  3. Validated<E, A>: Represents a value of type A that has been validated and may have associated errors of type E. It's similar to Either, but specifically designed for validation scenarios where you might have multiple errors. It should be parameterized by two types E and A.

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, and Validated.
  • 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 an Either to 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, or Validated objects. 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, and Validated.
  • 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 Validated type is a more advanced concept. If you find it challenging, prioritize implementing Maybe and Either correctly first.
  • The flatMap function for Either is a powerful tool for chaining operations that can potentially fail.
Loading editor...
typescript