Skip to content

Proposal: Overload Function.bind for when no argArray is provided. #22669

Description

@danthedaniel

Background

Currently the Function.bind type definition defines only one signature:

bind(this: Function, thisArg: any, ...argArray: any[]): any;

This destroys typing information on the bound function. Given difficulties in generating a new type for the bound function (the number of arguments has changed) this makes sense. However, it's very common to bind functions without using the argArray argument to bind. In this case a better solution can be made.

Proposal

I propose that bind be overloaded with a second signature:

bind<T extends Function>(this: T, thisArg: any): T;
bind(this: Function, thisArg: any, ...argArray: any[]): any;

Example

To use a real world example on how this improves TypeScript I'll pull something from a personal project.

Types used:

type SocketMsg = Guess | StateMsg;
type ChannelCallback<T> = (payload: T, ref: Ref, joinRef: Ref) => any;

export class Channel<T> {
  // ...
  on(event: ChannelEvent, callback: ChannelCallback<T>): void;
  // ...
}
export default class GuessView extends Component<GuessProps, GuessState> {
  channel: Channel<SocketMsg>;

  connectSocket() {
    ...
    this.channel.on("guess", this.gameGuess.bind(this));
  }

  gameGuess(guess: Guess) {
    let newState = Object.assign({}, this.state);
    newState.guesses.push(guess);
    this.setState(newState);
  }
}

Under the current bind signature a change to gameGuess such that it accepts a different type would produce no error:

  connectSocket() {
    ...
    this.channel.on("guess", this.gameGuess.bind(this)); // Now being provided the wrong type of function
  }
  gameGuess(guess: number) {
  // ...

But with bind utilizing a special definition for the case of only using the thisArg argument - the above example would produce an error.

Argument of type '(guess: number) => void' is not assignable to parameter of type 'ChannelCallback'. Types of parameters 'guess' and 'payload' are incompatible. Type 'SocketMsg' is not assignable to type 'number'. Type 'Guess' is not assignable to type 'number'.

Activity

  1. lierdakil commented on Apr 22, 2018

    @lierdakil

    I'm in favour, I have been adding similar interface augmentation into my projects for ages.

    I have to point out though that since bind is defined on interface Function, extends Function is extraneous. this type will always extend Function.

    FWIW, with TypeScript 2.8, it is possible to construct a proper type for bind via conditional types, albeit with a limit on a maximum number of arguments: https://gist.github.com/lierdakil/92256af3fae9fea9cf43688d0b98edcb#file-lib-d-ts -- here it works for functions with up to 10 arguments and after that falls back to ...args: any[]. The limit of 10 is arbitrary and can be easily changed.

    P.S. I should note, it'd be possible to avoid horribleness above if we had variadic generic types.

  2. ts2do commented on May 12, 2018

    @ts2do

    Echoing Nikolay Yakimov (@lierdakil): bind(this: Function, thisArg: any): this; would indeed accomplish the task at hand, but it still violates type safety insofar as there is nothing that checks that the function's this argument is of the type of thisArg. That being said, it would be much safer than the current declaration which is an easy 'gotcha' when using bind, as with @teaearlgraycold's original example. I've seen it suggested to always choose lambda closures over bind invocations for this reason. Obviously, that's not ideal since it causes the parameter declaration to bleed into places it doesn't need to be, making signature refactoring more difficult.

    A top-level function would give you far better safety in this regard:

    function getBound<T extends {[K1 in K]: Function}, K extends keyof T>(obj: T, member: K): T[K] {
      return obj[member].bind(obj);
    }

    There's no good way to declare the bind function to provide the same safety as my example function above unless TypeScript somehow captured the type from a property access resulting in a function, and it's probably not worth the trouble.

  3. kylemh commented on Nov 2, 2020

    @kylemh

    I would love to get this implemented, but the automated PR checks said unless this issue is signed off and added to the next version's requested changes, my PR wouldn't get merged.

    This is particularly annoying when leveraging Storybook's CSF syntax as they recommend using .bind({}) to create many stories.

    Ryan Cavanaugh (@RyanCavanaugh) let me know if this is something I can work on.

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

    In DiscussionNot yet reached consensusSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions