Hone logo
Hone
Problems

Implement a Generic Result Type in TypeScript

Functional programming paradigms often utilize a Result type to represent the outcome of an operation that can either succeed with a value or fail with an error. This is a powerful pattern for handling errors in a more explicit and manageable way compared to traditional exception handling. Your task is to implement a generic Result type in TypeScript that encapsulates these success and failure states.

Problem Description

You need to create a TypeScript discriminated union (or a similar pattern) that represents a Result. This Result type should be generic and accept two type parameters: T for the success type and E for the error type.

The Result type should have two distinct variants:

  1. Ok<T>: Represents a successful operation, holding a value of type T.
  2. Err<E>: Represents a failed operation, holding an error value of type E.

You will also need to implement methods on this Result type to allow for:

  • Checking if the Result is an Ok or an Err.
  • Extracting the value if it's Ok, or providing a default if it's Err.
  • Mapping a function over the success value if the Result is Ok.
  • Handling both Ok and Err cases gracefully with callback functions.

Key Requirements:

  • Define a generic Result type that can hold either a success value (T) or an error value (E).
  • Provide mechanisms to distinguish between Ok and Err states.
  • Implement methods to safely access the success value or handle the error.
  • Implement a map method that applies a function to the success value if present.

Expected Behavior:

  • When an operation is successful, it should return an Ok variant containing the result.
  • When an operation fails, it should return an Err variant containing the error information.
  • Methods should behave predictably based on whether the Result is Ok or Err.

Edge Cases:

  • What happens if you try to access the value of an Err?
  • What happens if you try to map a function over an Err?
  • Consider null or undefined as potential T or E types.

Examples

Example 1: Basic Usage

// Assume a function that might succeed or fail
function divide(a: number, b: number): Result<number, string> {
  if (b === 0) {
    return Err("Division by zero is not allowed.");
  }
  return Ok(a / b);
}

const successResult = divide(10, 2); // Ok<number>
const failureResult = divide(10, 0); // Err<string>

// Checking the state
if (successResult.isOk()) {
  console.log("Success:", successResult.unwrap()); // Output: Success: 5
}

if (failureResult.isErr()) {
  console.log("Error:", failureResult.unwrapErr()); // Output: Error: Division by zero is not allowed.
}

Example 2: Using map

const result = Ok(5);
const mappedResult = result.map(x => x * 2); // Ok<number>

const errorResult = Err("Something went wrong");
const mappedErrorResult = errorResult.map(x => x * 2); // Err<string>

console.log(mappedResult.unwrap()); // Output: 10
console.log(mappedErrorResult.unwrapErr()); // Output: Something went wrong

Example 3: Using match (or similar pattern)

function processResult(res: Result<number, string>): string {
  return res.match({
    Ok: (value) => `Processed value: ${value}`,
    Err: (error) => `Encountered error: ${error}`
  });
}

console.log(processResult(Ok(100))); // Output: Processed value: 100
console.log(processResult(Err("Network error"))); // Output: Encountered error: Network error

Constraints

  • The implementation must be entirely in TypeScript.
  • The Result type should be generic (Result<T, E>).
  • The implementation should aim for clarity and maintainability.
  • There are no strict performance constraints, but avoid overly inefficient operations.

Notes

  • Consider using discriminated unions for the Ok and Err variants.
  • Think about how to make the unwrap() and unwrapErr() methods throw specific errors when called on the wrong variant to signal programmer error.
  • The match method (or a similar approach) is a common functional pattern for handling both cases of a Result. You can name it something else if you prefer, but its functionality should be to take two functions (one for Ok, one for Err) and return the result of whichever function is appropriate.
  • You might want to create helper functions or static methods to construct Ok and Err instances.
Loading editor...
typescript