12

I would like to define an array type that allows different types depending on position, but in a repeating, alternating manner as found in some data structures.

Example:

[A, B, A, B, ...]
[A, B, C, A, B, C, ...]

Is this possible?

I know that I can define it for arrays with a fixed number of elements like above (without the ellipsis), and

(A | B)[]

would on the other hand allow any element to be either of type A or B.

I tried these:

[(A, B)...]
[...[A, B]]
[(A, B)*]
6
  • You can't. Tuples have different types by index but fixed length, arrays don't let you define alternating types. It seems like your data structure is weird, why not use e.g. [A, B][], an array of tuples? Commented Apr 11, 2020 at 10:43
  • Thanks. :-( It's for Elasticsearch bulk operations. Commented Apr 11, 2020 at 12:41
  • 1
    This is a good question. Wish there was a solution to this. Commented Oct 15, 2020 at 22:41
  • This question might be a duplicate of stackoverflow.com/questions/67315596/… Commented Nov 1, 2021 at 21:05
  • @Arc could you please send me the link to elastic documentation with such requirement? Commented Nov 18, 2021 at 20:22

2 Answers 2

10

I came up with something that "works", but it's kinda crazy:

type Alternating<T extends readonly any[], A, B> =
  T extends readonly [] ? T
  : T extends readonly [A] ? T
  : T extends readonly [A, B, ...infer T2]
    ? T2 extends Alternating<T2, A, B> ? T : never
  : never

This requires TypeScript 4.1+ because of the recursive conditional type.


Naive usage requires duplicating the value as a literal type for the T parameter, which is not ideal:

const x: Alternating<[1, 'a', 2], number, string> = [1, 'a', 2]

That seems strictly worse than just writing out [number, string, number] as the type. However with the help of a dummy function it's possible to avoid repetition:

function mustAlternate<T extends readonly any[], A, B>(
  _: Alternating<T, A, B>
): void {}

const x = [1, 'a', 2] as const
mustAlternate<typeof x, number, string>(x)

Here's a live demo with some test cases.


I wouldn't actually recommend relying on this in typical codebases (it's awkward to use and the error messages are terrible). I mostly just worked through it to see how far the type system could be stretched.

If anyone has suggestions for how to make it less wonky, I'm all ears!


Update

If you're using TypeScript 4.9+ you can ditch the mustAlternate function in favor of the satisfies operator. Here's an example.

Sign up to request clarification or add additional context in comments.

Comments

6
+100

Alternative approach:

type MAXIMUM_ALLOWED_BOUNDARY = 50

type Mapped<
    Tuple extends Array<unknown>,
    Result extends Array<unknown> = [],
    Count extends ReadonlyArray<number> = []
    > =
    (Count['length'] extends MAXIMUM_ALLOWED_BOUNDARY
        ? Result
        : (Tuple extends []
            ? []
            : (Result extends []
                ? Mapped<Tuple, Tuple, [...Count, 1]>
                : Mapped<Tuple, Result | [...Result, ...Tuple], [...Count, 1]>)
        )
    )



type Result = Mapped<[string, number, number[]]>

// 3 | 6 | 9 | 12 | 15 | 18 | 21 | 24 | 27 | 30 | 33 | 36 | 39 | 42 | 45 | 48
type Test = Result['length']

/**
 * Ok
 */
const test1: Result = ['a', 42, [1]]
const test2: Result = ['a', 42, [1], 'b', 43, [2]]

/**
 * Fail
 */
const test3:Result = ['a'] // error

const fn = <T, U>(tuple: Mapped<[T, U]>) => tuple

fn([42, 'hello']) // ok
fn([42, 'hello','sdf']) // expected error

Playground

Mapped - ugly name but does the job :D. Creates a union of all allowed states of the tuple. Each allowed tuple state has a length which can be divided by 3: length%3===0 // true. You can define any tuple you want, with 4 values, 5 etc ...

Every iteration I\m increasing Count array by 1. That's how I know when to stop recursive iteration.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.