Unordered tuple type

假装没事ソ 提交于 2021-02-10 14:46:34

问题


I am just digging into Typescript typings and I wondered how to define a type which is a tuple but with unordered element types.

I mean, having

type SimpleTuple = [number, string];

const tup1: SimpleTuple = [7, `7`]; // Valid
const tup2: SimpleTuple = [`7`, 7]; // 'string' is not assignable to 'number'
                                    // and vice-versa

This is useful in many cases, but what if I don't care about order or I need it to be unordered.
The example above is quite trivial since I could define

type SimpleUnorderedTuple = [number, string] | [string, number];

const tup1: SimpleUnorderedTuple = [7, `7`]; // Valid
const tup2: SimpleUnorderedTuple = [`7`, 7]; // Valid

However, I may have a bunch of types... A combinatory logic uppon them would be painful

type ABunchOfTypes = 'these' | 'are' | 'some' | 'words' | 'just' | 'for' | 'the' | 'example';
type ComplexUnorderedTuple =
    ['these', 'are', 'some', 'words', 'just', 'for', 'the', 'example'] |
    ['these', 'are', 'some', 'words', 'just', 'for', 'example', 'the'] |
    // and so on ...

This is insane. There are !n possible combinations, where n is the number of elements (I guess, I am not too good at maths!).

I am trying to achieve something like

type ABunchOfTypes = 'these' | 'are' | 'some';
type UnorderedTuple<T> = ; //...

type ComplexUnorderedTuple = UnorderedTuple<ABunchOfTypes>;

I found in this article

Any subsequent value we add to the tuple variable can be any of the predefined tuple types in no particular order.

But I couldn't reproduce. If I define a tuple of two elements, I am not allowed to access to the nth position, if n is greater than (or equal) the tuple length.


回答1:


If you are looking for permutation type of a union, this will give you exactly that:

type ABunchOfTypes = 'these' | 'are' | 'some' | 'words' | 'just';

type PushFront<TailT extends any[], HeadT> =
    ((head: HeadT, ...tail: TailT) => void) extends ((...arr: infer ArrT) => void) ? ArrT : never;

type CalculatePermutations<U extends string, ResultT extends any[] = []> = {
    [k in U]: (
        [Exclude<U, k>] extends [never] ?
        PushFront<ResultT, k> :
        CalculatePermutations<Exclude<U, k>, PushFront<ResultT, k>>
    )
}[U];

var test: CalculatePermutations<ABunchOfTypes> = ['are', 'these', 'just', 'words', 'some'];

You can try it in this Playground.

There is a limitation to this approach, however; my experiment showed that TypeScript can at most process a union of 7 strings. With 8 or more strings, an error will shown.

Update

If "no repetition" is what is needed, it is a lot simpler.

type ABunchOfTypes = 'these' | 'are' | 'some' | 'words' | 'just';

type PushFront<TailT extends any[], HeadT> =
    ((head: HeadT, ...tail: TailT) => void) extends ((...arr: infer ArrT) => void) ? ArrT : never;

type NoRepetition<U extends string, ResultT extends any[] = []> = {
    [k in U]: PushFront<ResultT, k> | NoRepetition<Exclude<U, k>, PushFront<ResultT, k>>
}[U];

// OK
var test: NoRepetition<ABunchOfTypes> = ['are', 'these', 'just', 'words', 'some'];
test = ['are', 'these', 'just'];
test = ['are'];

// Not OK
test = ['are', 'these', 'are'];

See this Playground Link.

Also, with the upcoming TypeScript 4 syntax, it can be simplified still:

type ABunchOfTypes = 'these' | 'are' | 'some' | 'words' | 'just';

// for TypeScript 4 
type NoRepetition<U extends string, ResultT extends any[] = []> = {
    [k in U]: [k, ...ResultT] | NoRepetition<Exclude<U, k>, [k, ...ResultT]>
}[U];

// OK
var test: NoRepetition<ABunchOfTypes> = ['are', 'these', 'just', 'words', 'some'];
test = ['are', 'these', 'just'];
test = ['are'];

// Not OK
test = ['are', 'these', 'are'];

See this Playground Link.

Update 2

The above two assumes that you require the array to be non-empty. If you also want to allow empty array, you can do it like this:

type ABunchOfTypes = 'these' | 'are' | 'some' | 'words' | 'just';

// for TypeScript 4 
type NoRepetition<U extends string, ResultT extends any[] = []> = ResultT | {
    [k in U]: NoRepetition<Exclude<U, k>, [k, ...ResultT]>
}[U];

// OK
var test: NoRepetition<ABunchOfTypes> = ['are', 'these', 'just', 'words', 'some'];
test = ['are', 'these', 'just'];
test = ['are'];
test = [];

// Not OK
test = ['are', 'these', 'are'];

See this Playground Link.

It is possible to replace U extends string by U extends keyof any to support union types of strings, numbers and symbols, but the current limitation of TypeScript makes it impossible to go beyond this.




回答2:


Mu-Tsun Tsai's answer seems to be a good starting point.

type ABunchOfTypes = 'these' | 'are' | 'some' | 'words' | 'just';

type PushFront<TailT extends any[], HeadT> =
((head: HeadT, ...tail: TailT) => void) extends ((...arr: infer ArrT) => void) ? ArrT : never;

type CalculatePermutations<U extends string, ResultT extends any[] = []> = {
    [k in U]: (
        [Exclude<U, k>] extends [never] ?
        PushFront<ResultT, k> :
        CalculatePermutations<Exclude<U, k>, PushFront<ResultT, k>>
    ) | PushFront<ResultT, k>
}[U];

var test1: CalculatePermutations<ABunchOfTypes> = ['are', 'these', 'just', 'words', 'some'];
var test2: CalculatePermutations<ABunchOfTypes> = ['are', 'just', 'words'];
// next gives error
var test3: CalculatePermutations<ABunchOfTypes> = ['are', 'are'];



回答3:


Create an enumerate function like this:

type ValueOf<T> = T[keyof T];

type NonEmptyArray<T> = [T, ...T[]]

type MustInclude<T, U extends T[]> =
  [T] extends [ValueOf<U>]
    ? U
    : never;

const enumerate = <T>() =>
  <U extends NonEmptyArray<T>>(...elements: MustInclude<T, U>) =>
    elements;

Usage:

type Word = 'these' | 'are' | 'some' | 'words';

const test1 = enumerate<Word>()('are', 'some', 'these', 'words');
const test2 = enumerate<Word>()('words', 'these', 'are', 'some');
const test3 = enumerate<Word>()('these', 'are', 'some', 'words');
  • ✅ Empty lists are not allowed
  • ✅ All values must be present
  • ✅ Duplicates are not allowed
  • ✅ Every value must be a Word

Playground link



来源:https://stackoverflow.com/questions/61852773/unordered-tuple-type

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!