ОБНОВЛЕНИЕ для TS4.0:
TS4.0 представит вариативные типы кортежей, в которых конкатенация фиксированного числа кортежей A
, B
, C
, так же просто, как использовать [...A, ...B, ...C]
. Это означает, что Flatten<T>
можно реализовать что-то примерно так:
type ConcatX<T extends readonly (readonly any[])[]> = [
...T[0], ...T[1], ...T[2], ...T[3], ...T[4],
...T[5], ...T[6], ...T[7], ...T[8], ...T[9],
...T[10], ...T[11], ...T[12], ...T[13], ...T[14],
...T[15], ...T[16], ...T[17], ...T[18], ...T[19]
];
type Flatten<T extends readonly any[]> =
ConcatX<[...{ [K in keyof T]: T[K] extends any[] ? T[K] : [T[K]] }, ...[][]]>
type InputTuple = [A, B, [C, D], [A, B, D], [A, B, C, D, B], A];
type FlattenedTuple = Flatten<InputTuple>;
// type FlattenedTuple = [A, B, C, D, A, B, D, A, B, C, D, B, A]
Он по-прежнему не работает для произвольно длинных кортежей (или для крайних случаев, таких как кортежи с открытым концом), но он намного менее безумный, чем был раньше.
Ссылка на игровую площадку
PRE-TS4.0 ответ:
Система типов TypeScript на самом деле не предназначена для того, чтобы позволить вам это сделать. Наиболее очевидной реализацией чего-то вроде Flatten
был бы рекурсивный условный тип; это в настоящее время не поддерживается (см. microsoft / TypeScript # 26980). Вы можете это сделать, но нет гарантии, что он продолжит работать в будущих версиях TypeScript. И даже если у вас есть рабочая версия, очень легко сделать ее так, чтобы компилятор TypeScript вылетел из строя, что привело к исключительно долгому времени компиляции и даже зависанию и сбою компилятора. В первой версии Flatten
, которую я написал в качестве теста, была эта проблема, требующая бесконечной работы даже с выходным кортежем длиной 7 и иногда сообщающей об ошибках глубины инстанцирования типа.
Я думаю, что каноническая проблема GitHub для этой функции может быть microsoft / TypeScript # 5453, a предложение о поддержке переменной длины (произвольной длины) виды (в основном типы типов). На данный момент единственный официально поддерживаемый способ управления кортежами вариативным способом - это добавить фиксированное количество типов в начало с помощью кортежи в позициях отдыха / распространения.
Итак, официальный ответ - что-то вроде того, что вы не можете или не должны этого делать, но это не мешает людям это делать. Есть даже библиотека под названием ts-toolbelt
, которая выполняет всевозможные забавные рекурсивные вещи. под крышками, чтобы получить больше произвольных манипуляций с кортежами. Я думаю, что автор этой библиотеки действительно пытался убедиться, что производительность компилятора не пострадает, поэтому, если бы я действительно собирался использовать что-либо, я бы, вероятно, использовал эту библиотеку, а не писал ее сам. Один из инструментов на этом поясе называется Flatten<L>
, что кажется делать то, что хочешь. Но даже эта библиотека по-прежнему официально не поддерживается.
Тем не менее, я не мог устоять перед написанием собственной версии Flatten
, чтобы дать вам некоторое представление о том, насколько это опасно. Кажется, работает достаточно хорошо. Я ограничил его конкатенацией до 7 кортежей, а общая длина сглаженного вывода не может превышать 30 элементов. Он использует как итеративные, так и рекурсивные условные типы, последний из которых не поддерживается. Особенно умный человек может придумать способ сделать это полностью итеративно, но я либо не тот человек, либо мне потребуется слишком много времени, чтобы стать им. Хорошо, хватит преамбулы, вот она:
/*
codegen
var N = 30;
var range = n => (new Array(n)).fill(0).map((_,i)=>i);
var s = [];
s.push("type Add = ["+range(N).map(i => "["+range(N-i).map(j => i+j+"").join(",")+"]").join(",")+"];")
s.push("type Sub = ["+range(N).map(i => "["+range(i+1).map(j => i-j+"").join(",")+"]").join(",")+"];")
s.push("type Tup = ["+range(N).map(i => "["+range(i).map(_=>"0").join(",")+"]").join(",")+"];")
console.log(s.join("\n"))
*/
type Add = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [21, 22, 23, 24, 25, 26, 27, 28, 29], [22, 23, 24, 25, 26, 27, 28, 29], [23, 24, 25, 26, 27, 28, 29], [24, 25, 26, 27, 28, 29], [25, 26, 27, 28, 29], [26, 27, 28, 29], [27, 28, 29], [28, 29], [29]];
type Sub = [[0], [1, 0], [2, 1, 0], [3, 2, 1, 0], [4, 3, 2, 1, 0], [5, 4, 3, 2, 1, 0], [6, 5, 4, 3, 2, 1, 0], [7, 6, 5, 4, 3, 2, 1, 0], [8, 7, 6, 5, 4, 3, 2, 1, 0], [9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]];
type Tup = [[], [0], [0, 0], [0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]];
type Arr = readonly any[];
type Tail<T extends Arr> =
((...x: T) => void) extends ((h: infer A, ...t: infer R) => void) ? R : never
type Concat<T extends Arr, U extends Arr> = Tup[Add[T["length"]][U["length"]]] extends infer A ? {
[I in keyof A]: I extends keyof T ? T[I] : U[Sub[Extract<I, keyof Sub>][T["length"]]]
} : never
// in TS4.0, Tail and Concat can be simplified to
// type Tail<T extends Arr> = T extends [infer A, ...infer R] ? R : never;
// type Concat<T extends Arr, U extends Arr> = [...T, ...U];
type Tuplize<T> = { [K in keyof T]: T[K] extends any[] ? T[K] : [T[K]] }
type Flatten<T extends readonly any[], N extends number = 7> =
N extends 0 ? [] :
Tuplize<T> extends infer U ? U extends Arr ?
{ 0: [], 1: Concat<U[0], Extract<Flatten<Tail<U>, Extract<Sub[N][1], number>>, Arr>> }[
U extends [] ? 0 : 1] : never : never;
Первые три строки генерируются небольшим сценарием JS для создания набора фиксированных кортежей, представляющих операции сложения и вычитания чисел, а также для получения пустого кортежа заданной длины. Итак, Add[3][4]
должно быть 7
, Sub[7][3]
должно быть 4
, а Tup[3]
должно быть [0,0,0]
.
Отсюда я определяю Tail<T>
, который берет кортеж типа [1,2,3,4]
и удаляет первый элемент для создания [2,3,4]
, и Concat<T, U>
, который берет два кортежа и объединяет их (например, Concat<[1,2],[3,4]>
должно быть [1,2,3,4]
). Определение Concat
здесь чисто итеративное, так что это еще не незаконно.
Затем я делаю Tuplize<T>
, который просто проверяет, что каждый элемент кортежа T
сам по себе является массивом. Так что ваш [A, B, [C, D], [A]]
станет [[A],[B],[C,D],[A]]
. Это устраняет странные граничные условия, возникающие при выравнивании.
Наконец, я пишу нелегальный и рекурсивный Flatten<T>
. Я попытался ввести там ограничение на рекурсию; он будет работать только для длин до 7 или около того. Если вы попытаетесь увеличить это значение, просто изменив 7
на 25
, вы можете получить ошибки от компилятора. В любом случае, основной подход здесь состоит в том, чтобы выполнить своего рода reduce()
операцию на Tuplize<T>
: просто Concat
первый элемент Tuplize<T>
в Flatten
-ed версии Tail
из Tuplized<T>
.
Посмотрим на пример:
type InputTuple = [A, B, [C, D], [A, B, D], [A, B, C, D, B], A];
type FlattenedTuple = Flatten<InputTuple>;
// type FlattenedTuple = [A, B, C, D, A, B, D, A, B, C, D, B, A]
Выглядит неплохо.
Здесь есть всевозможные предостережения; он сглаживает только на один уровень в глубину (это то, о чем вы просили). Вероятно, это не распространяется на профсоюзы. Он может работать не так, как вы хотите, с readonly
или дополнительными кортежами или массивами. Он определенно не будет работать должным образом с кортежами, которые слишком длинные или произвольной длины. Он может не работать должным образом, если Луна полная, или если Юпитер совмещен с Марсом и т. Д.
Смысл приведенного выше кода - не в том, чтобы вы вставляли его в производственную систему. Пожалуйста, не делай этого. Просто чтобы немного повеселиться с системой типов и показать, что это нерешенная проблема.
Хорошо, надеюсь, что это поможет; удачи!
TypeScript опубликовал предложение для вариативных типов, которое сейчас является запланировано на 4.0, что сделает решение jcalz @ еще более потрясающим:
person
jcalz
schedule
21.01.2020