Hone logo
Hone
Problems

Type-Level Pattern Matching in TypeScript

Type-level programming allows us to perform computations and logic at compile time, leading to more robust and efficient code. This challenge asks you to implement a basic form of type-level pattern matching in TypeScript, enabling you to conditionally select types based on the structure of a given type. This is a powerful technique for creating highly generic and reusable type definitions.

Problem Description

You are tasked with creating a utility type called Match that performs type-level pattern matching. The Match type will take two arguments: a type to match (T) and a pattern object (Pattern). The Pattern object will contain a series of conditions and corresponding types. The Match type should evaluate the conditions against the input type T and return the type associated with the first condition that evaluates to true. If none of the conditions match, it should return never.

Key Requirements:

  • Pattern Object Structure: The Pattern object will have the following structure:

    type Pattern<T> = {
      condition: (arg: T) => boolean;
      result: T;
    } | Pattern<T>;
    
  • Condition Evaluation: The condition function within each pattern object should be evaluated against the input type T. The condition function receives the input type T as its argument.

  • Type Selection: If a condition evaluates to true, the result type associated with that pattern should be returned.

  • never for No Match: If none of the conditions evaluate to true, the Match type should return never.

Expected Behavior:

The Match type should effectively filter through the provided patterns, selecting the type associated with the first condition that holds true for the input type.

Edge Cases to Consider:

  • Empty pattern objects.
  • Conditions that always evaluate to true or false.
  • Complex type structures within the input type T.
  • Conditions that might not be applicable to all possible types.

Examples

Example 1:

type Input = { a: number; b: string };

type Pattern1 = {
  condition: (arg: Input) => arg.a > 0,
  result: { c: boolean }
} | {
  condition: (arg: Input) => arg.b === "hello",
  result: { d: number }
} | {
  condition: (arg: Input) => true,
  result: { e: string }
};

type Result1 = Match<Input, Pattern1>;
// Result1: { c: boolean }
// Explanation: The first condition (arg.a > 0) is evaluated. Assuming 'a' is positive, it evaluates to true, and the corresponding result type { c: boolean } is returned.

Example 2:

type Input2 = { a: number; b: string };

type Pattern2 = {
  condition: (arg: Input2) => arg.a < 0,
  result: { c: boolean }
} | {
  condition: (arg: Input2) => arg.b === "world",
  result: { d: number }
};

type Result2 = Match<Input2, Pattern2>;
// Result2: never
// Explanation: Neither condition evaluates to true for the given input type. Therefore, the type `never` is returned.

Example 3:

type Input3 = { a: number }

type Pattern3 = {
  condition: (arg: Input3) => true,
  result: { c: string }
}

type Result3 = Match<Input3, Pattern3>;
// Result3: { c: string }
// Explanation: The condition always evaluates to true, so the result type { c: string } is returned.

Constraints

  • The condition function must be a function that accepts the input type T and returns a boolean.
  • The result type must be a valid TypeScript type.
  • The Match type must be implemented using conditional types and type inference.
  • The solution should be as concise and readable as possible.
  • The solution should handle empty pattern objects gracefully (returning never).

Notes

Consider using recursive conditional types to iterate through the Pattern object. Think about how to effectively evaluate the condition function at the type level. The key is to leverage TypeScript's powerful type system to perform this pattern matching logic during compilation. This is a challenging problem that requires a good understanding of advanced TypeScript type features.

Loading editor...
typescript