Type-Level Maps in TypeScript
Type-level programming allows us to perform computations and manipulations on types themselves, rather than values at runtime. Implementing type-level maps is a fundamental building block for more advanced type-level techniques, enabling us to associate values with specific types. This challenge asks you to create a type-level map that allows you to store and retrieve values based on their corresponding types.
Problem Description
You are tasked with creating a type-level map using TypeScript's advanced type features. This map will be represented as a conditional type that associates a value with a specific type. The map should allow you to:
- Define a Map: Create a type that acts as the map, associating types with values.
- Lookup a Value: Given a type, retrieve the associated value from the map.
- Handle Missing Keys: If a type is not found in the map, return a default value (which you will also define as part of the map type).
The core of the solution will involve using conditional types and potentially distributive conditional types to achieve this type-level mapping. Consider how to handle cases where the input type is not present in the map.
Examples
Example 1:
type MyMap = {
'string': string;
'number': number;
'boolean': boolean;
defaultValue: string;
};
type Result1 = MyMap['string']; // 'string'
type Result2 = MyMap['number']; // 'number'
type Result3 = MyMap['boolean']; // 'boolean'
type Result4 = MyMap['symbol']; // 'string' (defaultValue)
Explanation: The MyMap type defines a map where 'string' maps to a string, 'number' maps to a number, 'boolean' maps to a boolean, and defaultValue specifies the value to return if a key is not found. Looking up a key that exists returns the associated value. Looking up a key that doesn't exist returns the defaultValue.
Example 2:
type AnotherMap = {
'a': 1;
'b': 'hello';
defaultValue: any;
};
type Result5 = AnotherMap['a']; // 1
type Result6 = AnotherMap['b']; // "hello"
type Result7 = AnotherMap['c']; // any (defaultValue)
Explanation: This demonstrates a map with different value types and a default value of any. The lookup behavior remains the same.
Example 3: (Edge Case - Empty Map)
type EmptyMap = {
defaultValue: never;
};
type Result8 = EmptyMap['string']; // never (defaultValue)
Explanation: This demonstrates the behavior when the map is empty. The defaultValue is always returned.
Constraints
- The
defaultValuemust be explicitly defined within the map type. - The map type should be a union of string literal types (representing the keys) and a
defaultValueproperty. - The lookup operation should be type-safe, meaning the compiler should be able to infer the type of the returned value.
- The solution should be reasonably performant in terms of type checking (avoiding excessively complex type computations that could lead to long compile times).
Notes
- Consider using distributive conditional types to handle unions of types effectively.
- Think about how to represent the map type in a way that is both flexible and easy to use.
- The
defaultValueis crucial for handling cases where a type is not found in the map. It should be a single, explicit value. - The keys of the map must be string literal types. This is a key constraint.
- Focus on creating a type-level solution, not a runtime solution. The goal is to manipulate types, not values.