3

I am trying to make a wrapper function which will take a function as an input and will return a newly typed function which allow both a list of parameters and an object which contain parameter names as keys.

I have written the following code for that. The code works as expected. The problem is that I have pass an additional type which contains the keys as parameter name and value as type of that parameter. I want to do this dynamically. I want to access the names of the parameters

//I want remove this Args parameters and make it dynamically using F type.
function wrapper<F extends (...args: any) => any, Args>(func: unknown) {
   type ParametersList = Parameters<F>;
   return func as (...args: [Args] | ParametersList) => ReturnType<F>;
}
const add = (x: number, y: number) => x + y;
const wrappedAdd = wrapper<typeof add, { x: number; y: number }>(add);

The Parameters function returns a named tuple(which is a new feature I guess). Is there any way we could get the names/labels of that tuple. You can also suggest any other way. Thanks.

###Edit: Ok I after some research I found out that we cannot get the names of the parameters of the function. So now my goal is to shorten my code a little bit. In the above code where I am passing an object in place of Args. I only want to pass an array of strings.

const wrappedAdd = wrapper<typeof add, ["x", "y"]>(add);

I want to generate an object dynamically using this array. Thanks

Maheer Ali
  • 35,834
  • 5
  • 42
  • 73
  • 1
    Not all arguments may even have names. What if `func` has a rest parameter just like `wrapper` does? What should the names of the arguments passed as the rest parameter be? Usually if you need named arguments, you should be passing an object instead of positional arguments. – cdhowie May 11 '21 at 17:17
  • @cdhowie We can consider that the function will always have named parameters not rest parameters like `add` function – Maheer Ali May 11 '21 at 17:19
  • @cdhowie I think you didn't get my question. Consider if there is a function with all named parameters. Is there any way we can get those names as typescript tuple or array type? – Maheer Ali May 11 '21 at 17:25
  • @cdhowie No this doesn't answer my question. I want names a typescript type not in a variable at runtime. – Maheer Ali May 11 '21 at 17:37
  • Parameter names are not observable in the type system except as documentation for IntelliSense. I’m sure there’s an existing q/a pair for this but I haven’t found it yet. – jcalz May 11 '21 at 19:42
  • Does your `wrapper` function not just blow up at runtime if you call it with a single object instead of a list of parameters? – jcalz May 11 '21 at 19:46
  • @jcalz Yes at runtime it will obviously not work. But I will write javascript code for that too. – Maheer Ali May 12 '21 at 00:40
  • I still don't know how to map `x` and `y` to parameters object – captain-yossarian from Ukraine May 12 '21 at 13:35
  • 1
    @captain-yossarian Oh. NP. I will go for some other approach. Thanks for your time – Maheer Ali May 12 '21 at 13:38

1 Answers1

2

I think it is better to define any types in global scope.



type Elem = any;

type Predicate<Key extends number, Value extends Elem> = Record<Key, Value>

type Reduce<
    Arr extends ReadonlyArray<Elem>,
    Result extends Record<string, any> = {}
    > = Arr extends []
    ? Result
    : Arr extends [infer H]
    ? Result & Predicate<0, H>
    : Arr extends readonly [...infer Tail, infer H]
    ? Tail extends ReadonlyArray<Elem>
    ? Reduce<Tail, Result & Predicate<Tail['length'], H>>
    : never
    : never;


function wrapper<F extends (...args: any) => any>(func: F):
    (...args: [Reduce<Parameters<F>>] | Parameters<F>) => ReturnType<F> {
    return func
}

const add = (x: number, y: string, z: number[]) => x + y;

const wrappedAdd = wrapper(add);
const add = (x: number, y: string, z: number[]) => x + y;

const wrappedAdd = wrapper(add)({ 0: 1, 1: 'hello', 2: [1] }); // ok
const wrappedAdd2 = wrapper(add)(1, 'hello', [1]); // ok


Btw, no need to use type assertion here. I mean you can get rid of as operator

If you are interested in more examples of such kind of types, you can take a look on my article

Here you have representation of Reduce type in almost pure JS:

const reduce = <T,>(arr: T[], cache = {}) => {
    if (arr.length === 0) {
        return cache
    }
    if (arr.length === 1) {
        const [head] = arr;
        return { ...cache, [0]: head }
    }
    const [head, ...rest] = arr;
    return reduce(rest, { ...cache, [rest.length]: head })
}

Playground

Like @jcalz said:

Parameter names are not observable in the type system except as documentation for IntelliSense

There is no way to infer parameter name in TS and put it into another type, so I decided to use index type.

0 for first argument, 1 for second, etc ...

Regarding using argument names:

Docs

There is one place where the differences begin to become observable though: readability.

They’re purely there for documentation and tooling.

const add = (x: number) => x + x

type Args = Parameters<typeof add>

type Infer = Args extends [infer R] ? R : never // number
type Infer2 = Args extends [x: infer R] ? R : never // number

Hence, I don't think it is possible in current version of TypeScript.