Skip to content

Mapped function types #33881

Closed
Closed
@Rziedreo145

Description

@Rziedreo145

Search Terms

mapped function types

Suggestion

Extend the mapped type concept and syntax to allow mapping to a function type.

Use Cases

I am working on a project utilizing vuex. That library has good type definitions, but its store object exposes a very loosely typed API, out of necessity. Some of the most commonly used functions look like this:

interface Dispatch {
  (type: string, payload?: any, options?: DispatchOptions): Promise<any>;
}
interface Commit {
  (type: string, payload?: any, options?: CommitOptions): void;
}

The result is that interactions with the store have essentially no type checking. Since the store typically contains much of the core functionality of an app, it's unfortunate to lack type checking on all arguments to, and returns from that store.

I want to wrap and expose those APIs as more type-constrained functions that only accept known action & mutation names, and the correct payload type and return type associated with each. The ultimate goal is to define a single type representing each collection of actions or mutations, and from there, map to object types that can be used to type check the vuex module that is implementing those actions and mutations, and also map to function types that can be used to wrap vuex's dispatch and commit functions.

More generally, I think this same concept could be applied to many general-purpose tools that expose loosely typed APIs.

To the best of my understanding, the current mapped types syntax only supports mapping to object types.

Examples

interface Person {
  name: string;
  age: number;
}

Starting with an object type like this:

interface PersonActions {
  setName: (name: string) => Person;
  setAge: (age: number) => Person;
}

...I want to use type mapping to map to a function type with overloads corresponding to each action in PersonActions, which would be equivalent to this:

type DispatchPersonAction = {
  (action: 'setName', name: string): Person;
  (action: 'setAge', age: number): Person;
}

To the best of my understanding, the mapped type syntax only supports mapping to object types.

type DispatchAction<T> = {
  [P in keyof T]: T[P] extends (...args: any) => any
    ? (action: P, ...args: Parameters<T[P]>) => ReturnType<T[P]>
    : never
};

Using the DispatchAction mapped type above with the PersonActions interface results in an object type with members setName and setAge:

type DispatchPersonAction = DispatchAction<PersonActions>;
// Equivalent to:
type DispatchPersonAction = {
  setName: (action: 'setName', name: string) => Person;
  setAge: (action: 'setAge', age: number) => Person;
}

Syntactically, I think this would require conceiving of an alternative way of defining a mapped type such that the P in keyof T portion doesn't result in a member name on the mapped type. I don't have an informed suggestion for this alternative syntax, but am hoping someone out there does!

Thanks!

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
    This wouldn't change the runtime behavior of existing JavaScript code
    This could be implemented without emitting different JS based on the types of the expressions
    This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
    This feature would agree with the rest of TypeScript's Design Goals.

Activity

ark120202

ark120202 commented on Oct 8, 2019

@ark120202

That's actually already possible, because intersection of function types is effectively the same as overloads and there's a UnionToIntersection hack (see #29594).

Playground

Rziedreo145

Rziedreo145 commented on Oct 8, 2019

@Rziedreo145
Author

Wow @ark120202, thanks for the super quick response including a proof of concept with my examples! I just plugged it back into my project and it works like a charm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @Rziedreo145@ark120202

        Issue actions

          Mapped function types · Issue #33881 · microsoft/TypeScript