Hone logo
Hone
Problems

Distributive Types in TypeScript: Union to Intersection

Distributive types are a powerful feature in TypeScript that allow you to apply a type transformation to each member of a union type individually. This challenge focuses on implementing a function that demonstrates the distributive nature of TypeScript's mapped types, specifically transforming a union type into an intersection type. Understanding distributive types is crucial for advanced type manipulation and creating more robust and type-safe code.

Problem Description

You are tasked with creating a TypeScript function called Distribute<T, U> that takes a union type T and a type constructor U as input. The function should return a new type that is the intersection of the results of applying the type constructor U to each member of the union type T. In essence, it should distribute the type constructor U across the union.

Key Requirements:

  • The function must correctly handle union types of any number of types.
  • The type constructor U should be a type that accepts a single type parameter and returns a new type. For this challenge, we'll use a simple type constructor that adds a prefix: property to the input type.
  • The resulting type should be an intersection of the transformed types.

Expected Behavior:

Given a union type A | B | C and a type constructor U that adds a prefix: string property, Distribute<A | B | C, U> should produce a type equivalent to (A & { prefix: string }) & (B & { prefix: string }) & (C & { prefix: string }).

Edge Cases to Consider:

  • Empty union types (though this is less common, it's good to consider).
  • Types within the union that already have the prefix property. The intersection should not introduce duplicate properties.

Examples

Example 1:

Input:
type A = { id: number };
type B = { name: string };
type C = { active: boolean };

type Result1 = Distribute<A | B | C, <T> => { prefix: string } & T>;

Output:
type Result1 = { id: number; prefix: string } & { name: string; prefix: string } & { active: boolean; prefix: string };

Explanation: The union A | B | C is distributed with the type constructor <T> => { prefix: string } & T. Each type in the union is transformed, and the results are intersected.

Example 2:

Input:
type A = { id: number, prefix: string };
type B = { name: string };

type Result2 = Distribute<A | B, <T> => { prefix: string } & T>;

Output:
type Result2 = { id: number; prefix: string } & { name: string; prefix: string };

Explanation: Type A already has the prefix property. The type constructor still applies, but the intersection doesn't introduce a duplicate prefix property.

Example 3: (Empty Union)

Input:
type Empty = never;

type Result3 = Distribute<Empty, <T> => { prefix: string } & T>;

Output:
type Result3 = { prefix: string };

Explanation: Even with an empty union, the type constructor should still be applied (to never, which effectively becomes {prefix: string}).

Constraints

  • The type constructor U must be a type that accepts a single type parameter T and returns a new type. It should be defined as U<T>.
  • The solution must be written in TypeScript.
  • The solution should be type-safe and avoid runtime errors.
  • While performance isn't a primary concern for this challenge, avoid unnecessarily complex or inefficient type manipulations.

Notes

  • This challenge requires a good understanding of conditional types, mapped types, and union/intersection types in TypeScript.
  • Consider using mapped types to iterate over the union type and apply the type constructor to each member.
  • The key to understanding distributive types is recognizing that TypeScript applies the type transformation to each member of the union individually before combining the results.
  • The type constructor U is intentionally simple for this challenge. In real-world scenarios, it could be more complex.
Loading editor...
typescript