Marthinus Engelbrecht
Marthinus Engelbrecht

Reputation: 451

Is there a way to declare a tuple without using the array literal type notation?

Ok, so this is mostly for my curiosity. See below code:

const a: [string, number] = ["string", 1]; //valid
const b: Array<string, number> = ["string", 1]; //invalid
const c: Array<[string, number]> = ["string", 1] //invalid, it means [["string", 1]]

There seems to be no way to declare a tuple unless you use [] notation. My next thought was to look for a tuple type, but there seems to be nothing built in.

You could declare a Tuple type like so: type Tuple<K, V> = [K, V] but that defeats the whole idea.

Also something like :

let d: Array<string | number> = ["string", 1]

Would compile but you would be able to assign something like ["string", "another string", 1, 2, "stuff"] to it.

Update 1: As indicated by @jcalz type Tuple<K, V> = [K, V] is incorrect in that it limits the number of elements of the tuple type to 2.

Upvotes: 1

Views: 161

Answers (1)

jcalz
jcalz

Reputation: 329418

What exactly "defeats the whole idea" of type Tuple<K, V>? Is it that Tuple<> doesn't allow an arbitrary number of type parameters? Or that it uses [] inside its definition?


Here is one possible path to a solution, depending on your use case:

type Tuple<A, B = never, C = never, D = never, E = never, F = never, G = never> =
  [G] extends [never] ? [F] extends [never] ? [E] extends [never] ?
  [D] extends [never] ? [C] extends [never] ? [B] extends [never] ?
  [A] : [A, B] : [A, B, C] : [A, B, C, D] : [A, B, C, D, E] :
  [A, B, C, D, E, F] : [A, B, C, D, E, F, G]

Here, Tuple<> is a conditional type which uses default generic parameters to allow you to specify anywhere between one and seven tuple elements:

const e: Tuple<string, number> = ["string", 1]; // okay

If you need more elements you can extend the above definition, although you can never get an arbitrary number of them without something like variadic kinds, which are not part of TypeScript (as of 2.9 anyway).

Or maybe this doesn't count because tuple syntax is being used inside the definition. If you want to completely avoid tuple syntax, you could try to make a type which is similar to a tuple... something like this for the two-element case:

interface FakeTuple2<A, B> extends Array<A | B> {
  readonly length: 2,
  0: A,
  1: B,
}

This is tuple-like enough for many purposes, depending on what you need:

const f: FakeTuple2<string, number> = ["string", 1]; // okay

And if you want a variable number of elements as well as the fake tuple, you could use both approaches above to get something like:

type StripNever<T> = Pick<T, { [K in keyof T]: T[K] extends never ? never : K }[keyof T]>
type FakeTuple<A, B = never, C = never, D = never, E = never, F = never, G = never> =
  Array<A | B | C | D | E | F | G> & 
  StripNever<{ 0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G }> & {
    length: [G] extends [never] ? [F] extends [never] ? [E] extends [never] ?
    [D] extends [never] ? [C] extends [never] ? [B] extends [never] ? 
    1 : 2 : 3 : 4 : 5 : 6 : 7
  }

which also works for one through seven elements and can be extended.


So those are some ideas for you. Hope they help. Good luck!

Upvotes: 3

Related Questions