Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mapped type should take a function form as well #21421

Closed
pleerock opened this issue Jan 26, 2018 · 6 comments
Closed

Mapped type should take a function form as well #21421

pleerock opened this issue Jan 26, 2018 · 6 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@pleerock
Copy link

According to #12114 mapped types currently support following forms:

{ [ P in K ] : T }
{ [ P in K ] ? : T }
{ readonly [ P in K ] : T }
{ readonly [ P in K ] ? : T }

I think it shall also at least support a function form:

{ ([ P in K ]) : T }
{ ([ P in K ]) ? : T }
{ ([ P in K])(entities: P[], someBoolean: boolean) ? : T }

Currently Im trying to implement a Functionize<T> interface which forces implementors to implement any property of the T, but make it a function with maybe additional arguments. Example:

interface User {
     name: string;
     age: number
}

I want to do Functionize which I want to give me:

{
       name(names: string[]): string;
       age(ages: number[]): number;
}

And I'm asking about following method signature:

type Functionize<T> = {
   [P in keyof T](values: T[])?: T[P];
};
@pleerock
Copy link
Author

pleerock commented Jan 26, 2018

Someone suggested to do:

type Functionize <T> = {
   [P in keyof T]: () => T[P];
};

however it does not work with extra function parameters, e.g.

type Functionize <T> = {
   [P in keyof T]: (values: T[]) => T[P];
};

compiler have no errors when values argument is not defined in the implementor of Functionize interface.

EDIT: it appears to work partially, if I define lets say name(names: boolean[]) instead of name(names: string[]) it will give me a compiler error, however if I do simply name() its not telling me that names is required parameter.

@jack-williams
Copy link
Collaborator

jack-williams commented Jan 26, 2018

however if I do simply name() its not telling me that names is required parameter.

I believe the issue is that a signature that accepts fewer inputs is assignable to one that accepts more (provided they agree on matching parameters and output). So the following is acceptable:

let f: () => number = () => 42;
let g: (x: number[]) => number = f;

In your specific example, the type { name: () => string } is assignable to the type { name: (names: string[]) => string }.

You get a compiler error when using boolean[] because they disagree on a shared parameter.

Also, from your requirements I think the definition of Functionize should be:

type Functionize <T> = {
   [P in keyof T]: (values: (T[P])[]) => T[P]; // or [P in keyof T]?: (values: (T[P])[]) => T[P] if you want optional properties 
};

(added a lookup on the type of values.

@pleerock
Copy link
Author

Also, from your requirements I think the definition of Functionize should be:

correct, sorry Im using a bit different code, I just wanted to provide an example and make this mistake.

In your specific example, the type { name: () => string } is assignable to the type { name: (names: string[]) => string }.

correct, that's exactly issue I have. Is it tracked, or is it by design?

@jack-williams
Copy link
Collaborator

I believe it's by design. From the spec:

M has a rest parameter or the number of non-optional parameters in N is less than or equal to the total number of parameters in M.

when defining whether call-signature N is a subtype of call-signature M.

Intuitively if a user writes a function of type () => number, then it can always ignore extra arguments given and still return a number. So it also works when used as the type (x: boolean) => number or (names: number[]) => number.

The only way I could see this being something you don't want is if the output of the function must come from the input of the function. So in the type:

{
       name(names: string[]): string;
       age(ages: number[]): number;
}

the output of name always comes from an element in names, and the output of age always comes from an element in ages. If this is something you want, then I think the most likely solution will be parametricity and generics. Instead, would the follow types for you work?

{
       name<X>(names: X[]): X;
       age<X>(ages: X[]): X;
}

The rules about adding extra inputs to a function signature still apply, but in this case it's impossible to create something of type X out of nothing (unless you cheat and use any). An implementor of the function will not be able to write a function with the type: <X>() => X; the only way to return an X is to use one that is given to you from the input.

@mhegazy
Copy link
Contributor

mhegazy commented Jan 30, 2018

here is the syntax for definitnon a mapped type with function typed properties:

type Funcs<T> = {[P in keyof T]?: (entities: P[], someBoolean: boolean) => T };

@mhegazy mhegazy added the Question An issue which isn't directly actionable in code label Jan 30, 2018
@pleerock
Copy link
Author

okay thank you guys, I think this issue can be closed.

@microsoft microsoft locked and limited conversation to collaborators Jul 3, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

3 participants