1

What I want to do is pass an array to a function and receive the values of this array as a tuple in another function.

myFunction(
    (a, b) => null,
    {
        params: [true, 1]
    }
);

In the example above, I want typings: a: boolean and b: number. Instead I receive a: boolean | number and b: boolean | number.

Please keep in mind that I want the length of params to be variable. Also [1, false, 'string', {}] should be possible.

As I have read this has something to do with mapped tuples? But I really don't get it.

My current implementation looks like this:

function myFunction<P extends any[]>(
    fn: (...params: P) => null, 
    options: { params: P }) {
    ...
}

--

What actually works is this:

myFunction(
    (a, b) => null,
    {
        params: [true, 1] as [boolean, number]
    }
);

But I really don't want to type-cast all the time to make it work :/

0

3 Answers 3

2

Update:

There is actually an easier way. Normally narrowing of an array to a tuple type is only possible by the caller of a function with as const (see original answer). We can move this responsibility to the generic type parameter P of function myFunction by including a tuple type as constituent of an uniontype, so the constraint now becomes P extends (ReadonlyArray<any> | readonly [any]).

function myFunction<P extends (ReadonlyArray<any> | readonly [any])>(
  fn: (...params: P) => null,
  options: { params: P }
) {
  fn(...options.params)
  // ...
}

myFunction((a, b) => null, {
  params: [true, 1]
});

// params: [boolean, number]
// fn: (a: boolean, b: number) => null

Playground

Credit goes to an answer by jcalz involving some "sorcery code" 🧙 I picked up here. Also have a look at the relevant feature suggestion.

Original answer:

Although I don't quite understand your use case, what about this?

// fn: (params_0: true, params_1: 1, params_2: Date) => null
myFunction((a, b) => null, {
  params: ([true, 1, new Date()]) as const
}); 

function myFunction<P extends readonly any[]>(
  fn: (...params: P) => null,
  options: { params: P }
) { fn(...options.params); }

You still use a const assertion, but don't have to manually write all tuple item types. I assume, you just pass on your params directly to the callback, as there would be no way to manipulate P inside myFunction body in a way, where you rely on the tuple item types, as P is only constrained to extend any[].

In general initialized arrays are widened to Array type by the compiler if you don't narrow them at initialization time with as const.

const options = {
  params: [3,4,"a"] // params: (string | number)[];
}

const optionsConst = {
  params: [3,4,"a"] as const // params: readonly [3, 4, "a"];
}

Playground

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

2 Comments

I updated my examples. I want the length of array to be variable.
wow, I'm pretty impressed! It just works as you described it. you saved my day :)
0

Your declaration code is correct.

This is the right way to invoke your function:

myFunction<[number, boolean]>(
  (a, b) => null, 
  {params: [1, true]}
);

1 Comment

I updated my examples. I want the length of array to be variable.
0

try this:

function myFunction<A, B, P extends [A, B]>(
  fn: (...params: P) => null,
  options: { params: P }) {
     ...
}

1 Comment

I updated my examples. I want the length of array to be variable.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.