typescriptcartesian-producttypescript4.0variadic-tuple-types

How to utilise TypeScript Variadic Tuple Types for a Cartesian Product function?


TypeScript 4.0 started to support the notion of Variadic Tuple Types, a nice type construct that can be used in e.g. concatenation functions. An example from the docs:

type Arr = readonly any[];

function concat<T extends Arr, U extends Arr>(arr1: T, arr2: U): [...T, ...U] {
  return [...arr1, ...arr2];
}

I am interested in whether this type construct can be used to type a Cartesian Product function. The function should then infer the (mixed) types from the arguments to produce its return type. So if I input [number[], string[]] I would expect the output to be of type [number, string][]. Multiple implementations for a Cartesian Product can be found in this thread, but none is strictly typed. Here is an example:

const cartesian =
  (...a) => a.reduce((a, b) => a.flatMap(d => b.map(e => [d, e].flat())));

An implementation I currently use does not utilise Variadic Tuple Types and needs explicit type casting:

const cartesian = <T extends any[]>(...arr: any[][]): T[] =>
  arr.reduce<T[]>(
    (a, b) => a.flatMap<T>(c => b.map<T>(d => [...c, d] as T)),
    [[]] as T
  );

const product = cartesian<[number, string]>([1, 2, 3], ['a', 'b', 'c']);

I am looking for a solution without explicit type casting and I think Variadic Tuple Types may be the appropriate type construct here.

Question

How can I use Variadic Tuple Types to infer the types of a Cartesian Product function?


Solution

  • I don't think Variadic Tuple Types actually improves the way we can type this. Typing this has actually been possible since support for mapping tuples was added in 3.1 (was probably possible before but not as cleanly).

    You will still need a type assertion in the actual implementation, but the call site will infer the argument types and produce the correct return type:

    type MapCartesian<T extends any[][]> = {
      [P in keyof T]: T[P] extends Array<infer U>? U: never
    }
    const cartesian = <T extends any[][]>(...arr: T): MapCartesian<T>[] =>
      arr.reduce(
        (a, b) => a.flatMap(c => b.map(d => [...c, d] )),
        [[]] 
      ) as MapCartesian<T>[];
    
    const product = cartesian([1, 2, 3], ['a', 'b', 'c']);
    
    

    Playground Link