Implementing Tagged Union Helpers in TypeScript
Tagged unions (also known as discriminated unions) are a powerful pattern in TypeScript that allow you to represent data that can take on one of several distinct shapes. This challenge focuses on creating helper functions to simplify working with tagged unions, specifically for type-safe extraction of data and type narrowing. Implementing these helpers will improve code readability and reduce boilerplate when dealing with complex data structures.
Problem Description
You are tasked with creating three helper functions to work with tagged unions in TypeScript. These functions will operate on a generic tagged union type T and a specific tag value Tag.
-
extract<Tag>(union: T): Extract<T, { tag: Tag }, never>: This function takes a tagged unionunionand a tag valueTag. It should return the specific variant of the union where thetagproperty matches the providedTag. If no such variant exists, it should returnnever. This function leverages TypeScript's conditional types and mapped types to achieve type safety. -
is<Tag>(union: T): union is Extract<T, { tag: Tag }, never>: This function takes a tagged unionunionand a tag valueTag. It should return a boolean indicating whether the providedunionis of the variant where thetagproperty matches the providedTag. This function utilizes TypeScript's type predicate functionality. -
getDiscriminator<T>(union: T): T['tag']: This function takes a tagged unionunionand returns the value of thetagproperty. This is useful for accessing the tag value without needing to perform type narrowing.
Key Requirements:
- Type Safety: The functions must be type-safe, leveraging TypeScript's type system to ensure correctness.
- Generics: The functions should be generic to work with any tagged union type.
- Tagged Union Assumption: Assume that all variants of the tagged union have a
tagproperty of typestring. - Error Handling: No explicit error handling is required. The type system should handle cases where the tag is not found.
Expected Behavior:
The functions should behave as described above, providing type-safe extraction and type narrowing capabilities for tagged unions. The extract function should return the specific variant or never if the tag is not found. The is function should return true if the union matches the tag, and false otherwise. The getDiscriminator function should return the tag value.
Examples
Example 1:
type Shape =
| { tag: 'circle'; radius: number }
| { tag: 'square'; sideLength: number }
| { tag: 'triangle'; base: number; height: number };
const circle: Shape = { tag: 'circle', radius: 5 };
const square: Shape = { tag: 'square', sideLength: 10 };
// Using extract
const extractedCircle = extract<"circle">(circle); // Type: { tag: "circle"; radius: number }
const extractedSquare = extract<"square">(circle); // Type: never
// Using is
const isCircle = is<"circle">(circle); // Type: true
const isSquare = is<"square">(circle); // Type: false
// Using getDiscriminator
const circleTag = getDiscriminator(circle); // Type: "circle"
Example 2:
type Result =
| { success: true; data: string }
| { success: false; error: string };
const successResult: Result = { success: true, data: "Operation successful" };
const failureResult: Result = { success: false, error: "Operation failed" };
// Using extract
const extractedSuccess = extract<true>(successResult); // Type: { success: true; data: string }
const extractedFailure = extract<false>(successResult); // Type: never
// Using is
const isSuccess = is<true>(successResult); // Type: true
const isFailure = is<false>(successResult); // Type: true
// Using getDiscriminator
const successTag = getDiscriminator(successResult); // Type: true
Example 3: (Edge Case - Tag Not Found)
type Event =
| { tag: 'click'; x: number; y: number }
| { tag: 'keydown'; key: string };
const clickEvent: Event = { tag: 'click', x: 10, y: 20 };
const extractedUnknown = extract<'unknown'> (clickEvent); // Type: never
const isUnknown = is<'unknown'> (clickEvent); // Type: false
Constraints
- The
tagproperty of each variant in the tagged union must be of typestringor a boolean. - The input
unionmust be a valid tagged union type. - The
Tagtype must be a literal type corresponding to a valid tag value within the union. - Performance is not a primary concern for this challenge. Focus on type safety and clarity.
Notes
- Consider using conditional types and mapped types to achieve the desired type transformations.
- The
isfunction requires a type predicate. Remember to use theunion is ...syntax. - Think about how to leverage TypeScript's type inference capabilities to simplify the implementation.
- The
Extractutility type is crucial for theextractfunction. Understand how it works. - This challenge is designed to test your understanding of advanced TypeScript features related to tagged unions and type manipulation.