Type-Level Pattern Matching with Discriminated Unions
TypeScript's type system is powerful and allows us to define complex relationships between types. This challenge focuses on leveraging discriminated unions to achieve a form of type-level pattern matching. This is incredibly useful for creating robust and type-safe APIs that handle different states or variants of a data structure in a predictable way.
Problem Description
Your task is to implement a function that processes data based on its specific "type" property, similar to how you would pattern match in functional programming languages. You will be working with discriminated unions to represent different shapes of data. The function should be type-safe, ensuring that you can only access properties specific to the matched type.
Requirements:
- Define Discriminated Union: Create a discriminated union type that represents at least three different "shapes" of data. Each shape must have a common literal property (the "discriminant") that uniquely identifies it. Include other distinct properties for each shape.
- Implement a Processing Function: Create a function that accepts an argument of this discriminated union type.
- Type-Level Pattern Matching: Inside the function, use TypeScript's control flow analysis to "pattern match" on the discriminant property. This means writing conditional logic (e.g.,
ifstatements or aswitchstatement) that narrows down the type of the input argument based on the value of the discriminant. - Type Safety: Within each branch of your pattern matching, you must be able to safely access the properties specific to that particular shape. TypeScript should prevent you from accessing properties that don't exist on the narrowed type.
- Return Value: The function should return a string that describes the processed data in a human-readable format, incorporating properties from the matched shape.
Expected Behavior:
The function should correctly identify the shape of the input data and return a descriptive string. For example, if the input represents a "Circle," the output should clearly indicate it's a circle and include its radius.
Examples
Example 1:
// Input type definition (to be created as part of the challenge)
type Shape = { kind: 'circle', radius: number } | { kind: 'square', sideLength: number } | { kind: 'rectangle', width: number, height: number };
// Input data
const myCircle: Shape = { kind: 'circle', radius: 10 };
// Expected function call and output
// processShape(myCircle) would return "This is a circle with radius 10."
Example 2:
// Using the same Shape type as Example 1
// Input data
const mySquare: Shape = { kind: 'square', sideLength: 5 };
// Expected function call and output
// processShape(mySquare) would return "This is a square with side length 5."
Example 3:
// Using the same Shape type as Example 1
// Input data
const myRectangle: Shape = { kind: 'rectangle', width: 8, height: 6 };
// Expected function call and output
// processShape(myRectangle) would return "This is a rectangle with width 8 and height 6."
Constraints
- The discriminated union must include at least three distinct literal types for the discriminant property.
- Each member of the union must have at least one unique property in addition to the discriminant.
- The processing function must use type narrowing based on the discriminant property.
- The solution should be written purely in TypeScript.
Notes
- Consider using a
switchstatement for a clean implementation of pattern matching. - Think about how TypeScript's type inference helps in narrowing down types within conditional blocks.
- Ensure that you handle all possible cases of your discriminated union. A common way to ensure exhaustive checking is by using a
nevertype in a default case of a switch statement.