Определение списка кортежей с необязательными первым и последним элементами - PullRequest
2 голосов
/ 11 мая 2019

Я пытаюсь смоделировать некоторые данные в TypeScript, которые выглядят так:

// Sometimes we lookup the min/max dynamically, so we have keywords for them.
const intervalA: NumericInterval = [['$min', 3], [3, 5], [5, '$max']];

// Other times, we know the min/max and provide them inline
const intervalB: NumericInterval = [[0, 3], [3, 5], [5, 7]];

Я пытался определить NumericInterval как:

type MinInterval = ['$min', number];
type MaxInterval = [number, '$max'];
type Interval = [number, number];
type NumericInterval = [MinInterval?, ...Interval[], MaxInterval?];

Однако TypeScript не нравится, потому что A rest element must be last in a tuple.

Есть ли лучший способ выразить эту схему?

Ответы [ 2 ]

2 голосов
/ 11 мая 2019

Насколько я знаю, нет способа представить этот тип напрямую. Это подтип конкретного типа Array<["$min" | number, number | "$max"]>, который слишком широк и допускает такие вещи, как [[2, "$max"],[4, "$max"],["$min", 6]].

Вы можете использовать универсальные , сопоставленные и условные типы для представления желаемой формы в качестве ограничения на типы массивов, но это довольно уродливо / утомительно / сложно, и вам придется сделать все, что производит или принимает тип, универсальным. Я мог бы также показать один способ сделать это без особых объяснений (я всегда могу отредактировать более подробное объяснение, если вы действительно заботитесь об этом или хотите использовать это решение):

// get the tail of a tuple: Tail<[1,2,3]> is [2,3]
type Tail<L extends any[]> = ((...x: L) => any) extends
    ((h: any, ...t: infer T) => any) ? T : never;

// verify that T is a valid NumericInterval
type VerifyNumericInterval<T> = T extends Array<any> ?
    { [K in keyof T]: [
        K extends '0' ? "$min" | number : number,
        K extends keyof Tail<T> ? number : number | "$max"
    ] } : Array<["$min" | number, number | "$max"]>

// helper function to ensure parameter is a valid NumericInterval
const asNumericInteral = <T extends any[] | [any]>(
    numericInterval: T & VerifyNumericInterval<T>
): T => numericInterval;

И давайте проверим это:

asNumericInteral([]); // okay, zero length tuple
asNumericInteral([[1, 2]]); // okay
asNumericInteral([["$min", 2]]); // okay
asNumericInteral([[1, "$max"]]); // okay
asNumericInteral([["$min", "$max"]]); // okay, not sure if you want it to be
asNumericInteral([["$max", 2]]); // error!
//                 ~~~~~~ <-- string not assignable to never
asNumericInteral([[1, 2], [3, "$max"]]); // okay
asNumericInteral([["$min", 2], [3, "$max"]]); // okay
asNumericInteral([["$min", 2], [3, "$max"], [5, 6]]); // error!
//                                 ~~~~~~ <-- string not assignable to number

Все это ведет себя так, как я ожидаю для этого типа. Кстати, это будет полезно только для вызывающих функций, которые ожидают NumericInterval типов. Внутри реализаций, которые имеют значение универсального типа T & VerifyNumericInterval<T>, вам, скорее всего, придется иметь дело с крайними случаями самостоятельно. Существует небольшая вероятность, что компилятор сможет рассуждать о неразрешенном универсальном типе достаточно хорошо, чтобы заметить, скажем, следующее:

function hmm<T>(numInt: T & VerifyNumericInterval<T>) {
    for (let i = 0; i < numInt.length; i++) { // okay, numInt is known to be aray
        const interval = numInt[i]; // interval is type [number | "$min", "$max" | number]
        if (i !== 0) { // can't be "$min", right?
            interval[0].toFixed(); // error?! 
            // but it has to be a number, why doesn't the compiler know it?!
        }

        // manually check
        if ((i !== 0) && (typeof interval[0] === "number")) {
            interval[0].toFixed(); // okay now
        } 
    }
}

В этой функции вы знаете, что кроме i === 0, numInt[i] - это пара, в которой первый элемент определенно равен number. Но компилятор не может понять это, поэтому вы должны пройти через дополнительные проверки (или использовать утверждения типа), чтобы помочь ему.

Хорошо, надеюсь, это поможет. Удачи!

0 голосов
/ 11 мая 2019

Возможно, вы можете представить его в виде карты следующим образом:

type min = "$min" | number;
type max = "$max" | number;

const NI = new Map<min, max>([["$min", 3], [2,3], [1,2], [5, "$max"]]);

console.log([...NI]);

В качестве альтернативы вы можете сделать свой массив на один уровень глубже, чтобы учесть средний спред в кортеже.

type all = [MinInterval[], Interval[], MaxInterval[]];

const min: MinInterval = ["$min", 3];
const max: MaxInterval = [5, "$max"];
const interval: Interval[] = [[5, 3], [3, 4]];
const a: all = [[min], [...interval], [max]];

console.log(a.flat(1));
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...