Hone logo
Hone
Problems

Implementing a Result Type in TypeScript

The Result type is a powerful pattern for handling operations that can either succeed with a value or fail with an error. It provides a type-safe way to represent the outcome of an asynchronous operation or a function that might encounter errors, avoiding the need for nested if/else statements or callbacks. This challenge asks you to implement a Result type in TypeScript, enabling cleaner and more robust error handling.

Problem Description

You are tasked with creating a generic Result type in TypeScript. This type should represent the outcome of an operation that can either succeed with a value of type T or fail with an error of type E. The Result type should be defined as a discriminated union, allowing TypeScript to infer the type of the outcome (success or failure) based on the structure of the value.

Key Requirements:

  • Generic Types: The Result type must be generic, accepting two type parameters: T for the success type and E for the error type.
  • Discriminated Union: The Result type should be a discriminated union with two members: Ok and Err.
  • Ok Member: The Ok member should have a kind property set to "Ok" and a value property of type T.
  • Err Member: The Err member should have a kind property set to "Err" and an error property of type E.
  • Type Safety: TypeScript should be able to correctly infer the types of Ok and Err based on the provided generic types.
  • Helper Functions (Optional but Recommended): Implement helper functions ok and err to easily create instances of the Result type.

Expected Behavior:

The Result type should allow you to represent the outcome of an operation in a type-safe manner. You should be able to use it to handle both successful and error cases without resorting to complex conditional logic.

Edge Cases to Consider:

  • What happens when T or E are complex types? The type inference should still work correctly.
  • How can you ensure that the kind property is always present and has the correct value?

Examples

Example 1:

Input:  A function that might return a number or an error string.
Output: Result<number, string>
Explanation: The Result type will represent either a successful number or an error string.

Example 2:

type Result<T, E> =
  | { kind: "Ok"; value: T }
  | { kind: "Err"; error: E };

function divide(a: number, b: number): Result<number, string> {
  if (b === 0) {
    return { kind: "Err", error: "Division by zero" };
  } else {
    return { kind: "Ok", value: a / b };
  }
}

const result1 = divide(10, 2); // Result<number, string>
const result2 = divide(5, 0);  // Result<number, string>

// TypeScript will infer the types correctly.

Example 3: (Edge Case - Complex Types)

type User = {
  id: number;
  name: string;
};

function fetchUser(id: number): Result<User, string> {
  // Simulate fetching a user
  if (id < 0) {
    return { kind: "Err", error: "Invalid user ID" };
  }
  return { kind: "Ok", value: { id: id, name: "John Doe" } };
}

const userResult = fetchUser(1); // Result<User, string>

Constraints

  • The Result type must be defined using a discriminated union.
  • The kind property must be a string literal type ("Ok" or "Err").
  • The helper functions ok and err (if implemented) should return instances of the Result type.
  • The solution should be written in TypeScript.
  • No external libraries are allowed.

Notes

  • Consider using type aliases to define the Result type.
  • Think about how to make the Result type as type-safe as possible.
  • The helper functions ok and err are optional, but they can make it easier to create instances of the Result type. They should accept the appropriate value or error and return a Result instance.
  • Focus on creating a clean, readable, and type-safe implementation.
Loading editor...
typescript