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

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

Open
danthedaniel opened this issue Mar 17, 2018 · 3 comments
Open
Labels
In Discussion Not yet reached consensus Suggestion An idea for TypeScript

Comments

@danthedaniel
Copy link

danthedaniel commented Mar 17, 2018

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'.

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript In Discussion Not yet reached consensus labels Mar 19, 2018
@lierdakil
Copy link

lierdakil commented Apr 22, 2018

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.

@ts2do
Copy link

ts2do commented May 12, 2018

Echoing @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.

@kylemh
Copy link

kylemh commented Nov 2, 2020

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.

@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
Labels
In Discussion Not yet reached consensus Suggestion An idea for TypeScript
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants