Hone logo
Hone
Problems

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.

  1. extract<Tag>(union: T): Extract<T, { tag: Tag }, never>: This function takes a tagged union union and a tag value Tag. It should return the specific variant of the union where the tag property matches the provided Tag. If no such variant exists, it should return never. This function leverages TypeScript's conditional types and mapped types to achieve type safety.

  2. is<Tag>(union: T): union is Extract<T, { tag: Tag }, never>: This function takes a tagged union union and a tag value Tag. It should return a boolean indicating whether the provided union is of the variant where the tag property matches the provided Tag. This function utilizes TypeScript's type predicate functionality.

  3. getDiscriminator<T>(union: T): T['tag']: This function takes a tagged union union and returns the value of the tag property. 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 tag property of type string.
  • 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 tag property of each variant in the tagged union must be of type string or a boolean.
  • The input union must be a valid tagged union type.
  • The Tag type 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 is function requires a type predicate. Remember to use the union is ... syntax.
  • Think about how to leverage TypeScript's type inference capabilities to simplify the implementation.
  • The Extract utility type is crucial for the extract function. Understand how it works.
  • This challenge is designed to test your understanding of advanced TypeScript features related to tagged unions and type manipulation.
Loading editor...
typescript