Search code examples
typescriptgenerics

typescript multiple generics of the same type


I have a lot of lists that need to be casted to maps so i decided to create a listToMap function:

export function listToMap<T>(
  list: T[],
  keyAccesFn: (listElement: T) => string): Map<string, T> {
  const reducer = (accumulator: Map<string, T>, currentValue: T) => accumulator.set(keyAccesFn(currentValue), currentValue);
  return list.reduce(reducer, new Map<string, T>());
};

the function takes a list and a function which returns a string for a list element. The result is a Map with the returned string of keyAccesFn as key for the listelement the function was used on.

Now it occured that i not only have to extract the Maps key of the listed Objects but the value itself. I decided to apporach this by passing another function into the listToMap function which might return a new Type. The function looks like this atm:

export function listToMap2<T1, T2>(
  list: T1[],
  keyAccesFn: (element: T1) => string,
  valueAccesFn: (element: T1) => T2 = (element: T1) => element): Map<string, T2> {
  const reducer = (accumulator: Map<string, T2>, currentValue: T1) => accumulator.set(keyAccesFn(currentValue), valueAccesFn(currentValue));
  return list.reduce(reducer, new Map<string, T2>());
};

The issue is: i want to provide a default valueAccessFn that just returns the list elemnt so i could replace listToMap with listToMap2, but can't as typescript does not accept that generic type T1 might be generic type T2. That annoys me espacially, as i have no other place where pass anything of T2.


Solution

  • There are two issues with the default you specify:

    1. Typescript will not use the default value for inference when you call the function, so even if you could set the default, it will not infer T1=T2
    2. Since T1 and T2 are different types, just based on the method definition it can't tell that T1 will be compatible with T2

    Both problems can be solved, the first by specifying a default value for T1, and the second by using a type assertion to any for the default value.

    export function listToMap2<T1, T2 = T1>(
      list: T1[],
      keyAccesFn: (element: T1) => string,
      valueAccesFn: ((element: T1) => T2) = ((element: T1) => element) as any
    ): Map<string, T2> {
      const reducer = (accumulator: Map<string, T2>, currentValue: T1) => accumulator.set(keyAccesFn(currentValue), valueAccesFn(currentValue));
      return list.reduce(reducer, new Map<string, T2>());
    };
    
    listToMap2([''], k=> k) // Map<string, string>
    listToMap2([''], k=> k, k=> k.length); //Map<string, number>