Сглаживание вложенного типа кортежа и сохранение порядка

Я пытаюсь добиться чего-то вроде этого:

type Type = object;
type TypeTuple = readonly Type[];

function flattenTuples<T extends readonly (Type | TypeTuple)[], R = Flatten<T>>(...tuples: T): R {
  // flatten tuple and return with correct ordering
  // example: flattenTuples(A, B, [C, D], [A]) => [A, B, C, D, A]
}

Если функция flattenTuples сгладит каждый кортеж в предоставленном параметре, а реализация типа Flatten<T> сделает то же самое и вернет кортеж, например. "as const" и сохранить порядок кортежа параметров. Мне нужен только 1 уровень выравнивания.

Еще раз пример (A, B и т. Д. - все разные конструкторы классов):

const flat = flattenTuples(A, B, [C, D], [A]);
// this would make the variable flat's type:
// [A, B, C, D, A]

Я попробовал ответить на аналогичный вопрос но его решение типа Flatten не сработало. В приведенном выше примере он производит тип [A, B, C | D, A]


person Frans Bstrom    schedule 21.01.2020    source источник


Ответы (2)


ОБНОВЛЕНИЕ для 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
comment
Похоже, для TS 4.0 запланированы вариативные типы, которые могут помочь в этом чище, как вы упомянули - person Frans Bstrom; 22.01.2020
comment
Пошел вперед и дал ответ тем, кто, вроде меня, задается вопросом, как будет выглядеть ваше потрясающее решение в мире вариативных типов и официальной поддержки рекурсивных условных типов. - person Reed Hermes; 19.06.2020
comment
площадка ссылку на код - person Reed Hermes; 19.06.2020

Однако это основано на экспериментальном предложении и не имеет управления рекурсией, как ответ jcalz @, и, по очевидным причинам, не следует использовать в производстве до рекурсивные условные типы фактически официально поддерживаются и до тех пор, пока не будет выпущена версия 4.0 и не будет завершен синтаксис для вариативных типов.

type Tuple = readonly any[]
type Tail<T extends Tuple> = T extends [any, ...infer U] ? U : []
type Concat<T extends Tuple, U extends Tuple> = [...T, ...U];
type Tuplize<T> = { [K in keyof T]: T[K] extends unknown[] ? T[K] : [T[K]] }

type Flatten<T extends Tuple> =
  Tuplize<T> extends infer U ?
    U extends Tuple ?
      { 0: [], 1: Concat<U[0], Flatten<Tail<U>>>}[U extends [] ? 0 : 1]
      : never
    : never;

Хотя мечтать весело, правда? ????

Ссылка на игровую площадку


Спасибо за подробный ответ и ссылку на проблему с github! Именно то, что мне нужно, жаль, что нет менее хитрого способа сделать это.

person Reed Hermes    schedule 19.06.2020