I'm attempting to create a factory function which accepts an array of { item: T; weight: number }
and returns an object with a method pick
, which in turn would return either T
or T[]
, based on whether it is called with a defined quantity
option or not:
type PickOneOptions = {
quantity: undefined;
};
type PickManyOptions = {
quantity: number;
};
type PickOptions = PickOneOptions | PickManyOptions;
type WeightedItem<T> = { item: T; weight: number };
type WeightedTable<T> = {
pick: {
(): T;
(options: PickOneOptions): T;
(options: PickManyOptions): T[];
};
};
However, using my current type definitions, TypeScript complains that 'T' could be instantiated with an arbitrary type which could be unrelated to 'T | T[]'.
in the method's implementation. Interestingly, casting either of the return values to any
(either one of the two possible branches) silences the issue.
The return values do work as expected whether I silence the error or not:
const createWeightedTable = <T>(items: WeightedItem<T>[]): WeightedTable<T> => {
const totalWeight = items.reduce((prev, curr) => prev + curr.weight, 0);
return {
pick: (options?: PickOptions) => { // 'T' could be instantiated with an arbitrary type which could be unrelated to 'T | T[]'.
const { quantity } = options ?? {};
const weightedItems = items;
let random = Math.random() * totalWeight;
if (quantity === undefined) {
for (const weightedItem of weightedItems) {
random -= weightedItem.weight;
if (random <= 0) {
return weightedItem.item;
}
}
throw new Error();
}
const result: T[] = [];
for (let i = 0; i < quantity; i++) {
for (const weightedItem of items) {
random -= weightedItem.weight;
if (random <= 0) {
result.push(weightedItem.item);
}
}
}
return result;
},
};
};
const table = createWeightedTable([
{
item: 'a',
weight: 1
},
{
item: 'b',
weight: 1
}
])
const resultOne = table.pick(); // string
const resultMany = table.pick({ quantity: 2 }); // string[]
What's causing that error? And how could I fix it without resorting to type assertion?