TL;DR
Is there a way to create an array of tuples where the second element is automatically derived from the first?
Check out the example playground to see the code below in action.
My scenario
I'm using a tool called Puppeteer to navigate through various Sudoku websites, solve the puzzles, and move on to the next site.
Each website is defined by a type called Puppet<TDiff>
, where TDiff
represents a specific difficulty level for that particular site. I have a function named runPuppet
that takes a Puppet
as input along with some options, including the difficulty level, and then executes the algorithm mentioned above.
run-puppet.ts
interface Puppet<TDiff extends string | undefined = undefined> {
// ...
}
interface RunOptions<TDiff extends string | undefined> {
difficulty: TDiff;
newGame: boolean;
// ...
}
const default options = {
newGame: false,
// difficulty is not defined
// ...
};
export default async function runPuppet<TDiff extends string | undefined = undefined>(
puppet: Puppet<TDiff>,
options: Partial<RunOptions<TDiff>>
) {
options = Object.assign({}, defaultOptions, options);
// ...
}
sudoku-com.ts (for sudoku.com)
type Difficulty = 'easy' | 'medium' | 'hard' | 'expert';
const SudokuDotComPuppet: Puppet<Difficulty> = {
// ...
}
websudoku-com.ts (for websudoku.com)
type Difficulty = 'easy' | 'medium' | 'hard' | 'evil';
const WebSudokuDotComPuppet: Puppet<Difficulty> = {
// ...
}
main.ts
(async () => {
await runPuppet(WebSudokuDotComPuppet, { difficulty: 'evil' });
await runPuppet(WebSudokuDotComPuppet, { difficulty: 'evil', newGame: true });
await runPuppet(WebSudokuDotComPuppet, { difficulty: 'evil', newGame: true });
await runPuppet(SudokuDotComPuppet, { difficulty: 'expert' });
await runPuppet(SudokuDotComPuppet, { difficulty: 'expert', newGame: true });
await runPuppet(SudokuDotComPuppet, { difficulty: 'expert', newGame: true });
})();
The current code successfully runs both puppets multiple times without any issues.
Now, my goal is to refactor the code in main.ts to utilize an array of tuples:
Array<[Puppet<TDiff>, TDiff]>
, where each tuple has its own unique difficulty value. This will allow me to achieve the following:
// needs refinement
type PuppetDifficulty<TDiff extends string | undefined = undefined> =
[Puppet<TDiff>, TDiff];
(async () => {
// throws compile-time errors
const puppets: PuppetDifficulty[] = [
[ WebSudokuDotComPuppet, 'evil' ],
[ SudokuDotComPuppet, 'expert' ],
];
for (const [puppet, difficulty] of puppets) {
for (let i = 0; i < 3; i++) {
await runPuppet(puppet, { difficulty, newGame: !!i });
}
}
})();
This snippet generates four errors indicating that 'expert' and 'evil' are not compatible with 'undefined'. This issue arises because when Puppet
does not have a <TDiff>
specified, it defaults to 'undefined' instead of inferring it based on the provided arguments.
I attempted to use the ElementType<T>
pattern:
type DifficultyType<TPuppet extends Puppet> =
TPuppet extends Puppet<infer T> ? T : undefined;
type PuppetDifficulty<TPuppet extends Puppet = Puppet> = [ TPuppet, DifficultyType<TPuppet> ];
(async () => {
const puppets: PuppetDifficulty[] = [
[ WebSudokuDotComPuppet, 'evil' ],
[ SudokuDotComPuppet, 'expert' ],
];
// ...
)();
However, this leads to the same set of errors encountered previously.