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
Ushould 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 aprefix: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
prefixproperty. 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
Umust be a type that accepts a single type parameterTand returns a new type. It should be defined asU<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
Uis intentionally simple for this challenge. In real-world scenarios, it could be more complex.