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
Resulttype must be generic, accepting two type parameters:Tfor the success type andEfor the error type. - Discriminated Union: The
Resulttype should be a discriminated union with two members:OkandErr. OkMember: TheOkmember should have akindproperty set to"Ok"and avalueproperty of typeT.ErrMember: TheErrmember should have akindproperty set to"Err"and anerrorproperty of typeE.- Type Safety: TypeScript should be able to correctly infer the types of
OkandErrbased on the provided generic types. - Helper Functions (Optional but Recommended): Implement helper functions
okanderrto easily create instances of theResulttype.
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
TorEare complex types? The type inference should still work correctly. - How can you ensure that the
kindproperty 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
Resulttype must be defined using a discriminated union. - The
kindproperty must be a string literal type ("Ok" or "Err"). - The helper functions
okanderr(if implemented) should return instances of theResulttype. - The solution should be written in TypeScript.
- No external libraries are allowed.
Notes
- Consider using type aliases to define the
Resulttype. - Think about how to make the
Resulttype as type-safe as possible. - The helper functions
okanderrare optional, but they can make it easier to create instances of theResulttype. They should accept the appropriate value or error and return aResultinstance. - Focus on creating a clean, readable, and type-safe implementation.