Hone logo
Hone
Problems

Implementing Monads in TypeScript

Monads are a powerful concept in functional programming that allow you to chain operations together while handling potential failures or side effects in a controlled manner. This challenge asks you to implement fundamental monad types (Maybe/Optional and Either) in TypeScript, demonstrating your understanding of monadic principles and their practical application. Successfully completing this challenge will provide a solid foundation for working with more complex functional patterns.

Problem Description

You are tasked with implementing two core monad types: Maybe (also known as Optional) and Either. These monads are used to handle situations where a computation might fail or return a value of different types.

Maybe/Optional Monad: Represents a value that might be present or absent (null/undefined). It's useful for handling situations where a value might not exist, preventing null pointer exceptions.

Either Monad: Represents a computation that can result in either a successful value (Right) or an error (Left). It's useful for handling errors gracefully and propagating them through a chain of operations.

Key Requirements:

  1. Maybe Type:

    • Should have a constructor that accepts a value of any type T.
    • Should have a method map<U>(f: (value: T) => U): Maybe<U> that applies a function f to the contained value if it exists, otherwise returns a Maybe containing undefined.
    • Should have a method orElse<U>(fallback: () => Maybe<U>): Maybe<U> that returns the original Maybe if it contains a value, otherwise returns the result of the fallback function.
    • Should have a method getOrElse(defaultValue: U): U that returns the contained value if it exists, otherwise returns the provided defaultValue.
  2. Either Type:

    • Should have a constructor that accepts either a Left<E> (representing an error) or a Right<R> (representing a successful value).
    • Should have a method map<U>(f: (value: R) => U): Either<E, U> that applies a function f to the contained value if it's a Right, otherwise returns an Either containing the original Left.
    • Should have a method orElse<U>(fallback: () => Either<E, U>): Either<E, U> that returns the original Either if it's a Right, otherwise returns the result of the fallback function.
    • Should have a method fold<U>(onLeft: (error: E) => U, onRight: (value: R) => U): U that applies either onLeft if the Either is a Left or onRight if it's a Right.
  3. Left and Right Types:

    • Left<E> should represent an error value of type E.
    • Right<R> should represent a successful value of type R.

Examples

Example 1: Maybe

Input: const maybeValue = new Maybe(5);
const mappedValue = maybeValue.map(x => x * 2);
const undefinedMaybe = new Maybe(undefined);
const mappedUndefined = undefinedMaybe.map(x => x * 2);

Output: mappedValue should be Maybe(10), mappedUndefined should be Maybe(undefined)
Explanation: The `map` function applies the provided function to the value inside the `Maybe` if it exists. If the `Maybe` is undefined, it returns a `Maybe` containing `undefined`.

Example 2: Either

Input: const rightValue = new Either(new Right("Success!"));
const leftError = new Either(new Left("Something went wrong!"));

const mappedRight = rightValue.map(x => x.toUpperCase());
const mappedLeft = leftError.map(x => x.toUpperCase());

Output: mappedRight should be Either(new Right("SUCCESS!")), mappedLeft should be Either(new Left("Something went wrong!"))
Explanation: The `map` function applies the provided function to the value inside the `Either` if it's a `Right`. If it's a `Left`, it returns the original `Either` with the `Left` value.

Example 3: Either with fold

Input: const rightValue = new Either(new Right(10));
const leftError = new Either(new Left("Error!"));

const foldedRight = rightValue.fold(err => `Error: ${err}`, val => `Value: ${val}`);
const foldedLeft = leftError.fold(err => `Error: ${err}`, val => `Value: ${val}`);

Output: foldedRight should be "Value: 10", foldedLeft should be "Error: Error!"
Explanation: The `fold` function applies the `onRight` function to the value inside the `Right` or the `onLeft` function to the error inside the `Left`.

Constraints

  • All methods should be implemented immutably (i.e., they should return new instances of the monad instead of modifying the existing one).
  • The code should be well-documented and easy to understand.
  • Type safety is crucial. Leverage TypeScript's type system effectively.
  • No external libraries are allowed.

Notes

  • Consider using generics to make your monad implementations reusable with different types.
  • Think about how to handle the chaining of operations effectively using the map and orElse methods.
  • The orElse method is crucial for handling potential failures gracefully.
  • The fold method in Either provides a concise way to handle both success and failure cases.
  • Focus on the core monadic principles: encapsulation, sequencing, and error handling.
Loading editor...
typescript