Typescript: bind(), call(), and apply() are untyped

Created on 23 Jul 2014  Â·  52Comments  Â·  Source: microsoft/TypeScript

bind() returns type 'any' and does not type check it's arguments. e.g.

var add = function (a: number, b: number) { return a + b; };
var addString = add.bind(null, 'hello');

compiles without error and 'addString' has type any.

bind is a default way to achieve partial application so it would be great to have it typechecked properly.

Similarly for apply, the code

var listA = [1, 2, 3];
var listB = ['a', 'b', 'c'];
listA.push.apply(listA, listB);

compiles without error and listA is still incorrectly marked as type 'number[]'.

call() example:

var add5 = function (a: number) { return 5 + a; };
add5.call(null, 'a string');

Again without a compiler error.

Fixed In Discussion Suggestion

Most helpful comment

Now implemented in #27028.

All 52 comments

It would be nice to type these more strongly but we will need a proposal for some options. We previously discussed this in some early design meetings on generics and weren't really able to come up with something suitable. At least using Function instead of 'any' where applicable might be a meaningful improvement. See Ander's response here for a similar example: https://typescript.codeplex.com/discussions/462819

This will return you a function with the same signature you passed in--and for memoize that's really the thing that matters. There's no generic way to extract the parameters and/or return type from a function type, so the hashFn parameter is probably best left as just Function.

With regards to 'bind' and 'call', what specifically do you need proposals for? Are there cases where the returned type could be ambiguous? Apologies if I'm missing something obvious.

Just a brief exploration of some of the issues here. We would need to fill in all the ?s along with descriptions of how those results would be achieved.

interface fn {
    (): string;
    (n: number): void;
    (n: number, m: number): Element;
    (k: string): Date;
}

var x: fn;
var y = x(); // y: string

var args = [3]; // args: number[]
var c1 = x.apply(window, args); // c1: ?
args = []; // arg: still number[]
var c2 = x.apply(window, args); // c2: ??

var b = x.bind(undefined, null); // b: what type?
var j = b(); // j: ?

call is less difficult to reason about, but it's probably the most unlikely to be used in a place where you'd need strong type information.

It seems like the apply method of a function just cannot return a typed value.
In order to determine the type we have to find a mapping of an array (arguments) to a number of tuples (parameters of different signatures from overloads). It can only be done if we know the number of elements in the array and their types. Having these 2 things we can view the array as a tuple. Matching tuples to tuples is a solvable problem. Now in order to know the number of elements in an array and their type at the compile time it has got to be an array literal (not an expression). Only in this case we can deduce the type, but restricting apply to array literals only would be a significant and pointless limitation. All this means that in order to have a typed version of apply there has to be a different syntax that takes array literals, or just tuples straight, and deduces the type of the result. In order not to introduce new syntax there can be a typed overload of apply that takes an array literal or just a list of arguments as they listed in the original function. Even when the original signature of a function matches the one of the apply method of that function it should not be a problem. In this case there will be no overloads, but just one single way to call it which is what it has now.

// typescript
f(a: number, b: string) {}
f.apply(<any>null, 10, 'hey');

// emitted javascript
f(a, b) {}
f.apply(null, [10, 'hey'])

Here is another example I came across where this is an issue:

interface Entity {}

class Example { 
    constructor( private lines: string[] ) {}

    mapLines(): Entity[][] {
        return this.lines.map( this.mapLine.bind( this ) ); 
    }

    mapLine( line: string ): Entity[] {
        return line.split( '' ).map( this.mapCharacter.bind( this ) );
    }

    mapCharacter( character: string ): Entity {
        return {};
    }
}

var instance = new Example( ["foo", "baz"] );
instance.mapLines();

The binds are needed because of the usage of this, but since bind returns any, the compiler will error out because of the mismatch of Entity{}[] and Entity[][].

The only solution I could think of is to force the type:

return <Entity[][]>this.lines.map( this.mapLine.bind( this ) );

@RyanCavanaugh
regarding:

call is less difficult to reason about, but it's probably the most unlikely to be used in a place where you'd need strong type information.

I find I have a lot of trouble with this issue, as I frequently factor my functions so that they contain small helper functions. Here's a simplified example to show the structure I use:

interface Options {a: string;}

class A {
    local: string;
    f(options: Options) {
        function helper(options: Options) {
            this.local = options.a;
        }
        helper.call(this, {b: ''}); // the error here is not reported by the compiler
    }   
}

Any helpers that reference this must be invoked via call().
This problem hits me hard when I refactor code, including renaming variables, which I do frequently as I come up with better naming.

I would greatly appreciate it if you could add support for type checking the arguments of a call().
(I also understand that apply() has too many ambiguities to support type checking).

EDITED: to add type helper

@psnider, if you want to avoid introducing a lexical this, you could explicitly capture the outer one

var _this = this;
function helper(opts: Options) {
    _this.a = // ...
}

or you could also use an arrow function.

var helper = (opts: Options) => {
    this.a = // ...
}

@DanielRosenwasser

awesome! Cleans things up nicely, solving two problems:

  • removes the need for call()
  • gives me back the nice type checking that I love TypeScript for

Thanks, I had overlooked using the fat-arrow syntax here,
although I use it all the time for callbacks and promises.

I can continue on with my refactoring addiction without fear!

For reference, the modified code, for which the compiler correctly reports the type error is:

interface Options {a: string;}

class A {
    local: string;
    f(options: Options) {
        var helper = (options: Options) => {
            this.local = options.a;
        }
        helper(this, {b: ''});
    }   
}

Ok how about this as a spec for bind:

Binding with:

  • args which do not match the original function type
  • too many args

results in compiler error: 'Supplied parameters do not match any signature of call target.' e.g.

var x: (a: typeA, b: typeB, c: typeC): returnType;
var a: typeA;
var b: typeB;
var c: typeC;
var d: typeD;

var boundX = x.bind(undefined, a, b, d); // COMPILER ERROR
var boundX' = x.bind(undefined, a, b, c, d); // COMPILER ERROR

For an overloaded function where there is ambiguity, 'bind' should return type any. e.g.

interface fn {
    (): string;
    (n: number): void;
    (n: number, m: number): Element;
    (k: string): Date;
}

var x: fn;
var boundX = x.bind(undefined, null); // boundX: any

For:

  • a function which is not overloaded
  • a function which is overloaded but bind is called unambiguously

bind returns the expected typed value:

var x: (a: typeA, b: typeB, c: typeC): returnType;
var a: typeA;
var b: typeB;

var boundX = x.bind(undefined, a); // boundX: (b: typeB, c: typeC): returnType;
var boundX' = x.bind(undefined, a, b); // boundX': (c: typeC): returnType;

interface fn {
    (): string;
    (n: number): void;
    (k: string): Date;
}

var y: fn;

var boundY = y.bind(undefined, 5); // boundY: (): void
var boundY' = y.bind(undefined, 'a string'); // boundY': (): Date

@psnider glad that solves your problem!

Now that we have tuple types, my personal opinion is that instead of trying to add magic to the typing for these functions, you should actually use typed functions.

var _apply = (f: any, args: any[], thisArg?: any) => f.apply(thisArg, args);
var apply$: <T, R>(f: (...values: T[]) => R, args: T[], thisArg?: any) => R = _apply
var apply2: <T1, T2, R>(f: (a1: T1, a2: T2) => R, args: [T1, T2], thisArg?: any) => R = _apply
var apply3: <T1, T2, T3, R>(f: (a1: T1, a2: T2, a3: T3) => R, args: [T1, T2, T3], thisArg?: any) => R = _apply;
var apply4: <T1, T2, T3, T4, R>(f: (a1: T1, a2: T2, a3: T3, a4: T4) => R, args: [T1, T2, T3, T4], thisArg?: any) => R = _apply;
var apply5: <T1, T2, T3, T4, T5, R>(f: (a1: T1, a2: T2, a3: T3, a4: T4, a5: T5) => R, args: [T1, T2, T3, T4, T5], thisArg?: any) => R = _apply;
// etc.

Or you could use overloads if you don't like explicit arities:

function apply<T1, T2, T3, T4, T5, R>(f: (a1: T1, a2: T2, a3: T3, a4: T4, a5: T5) => R, args: [T1, T2, T3, T4, T5], thisArg?: any): R;
function apply<T1, T2, T3, T4, R>(f: (a1: T1, a2: T2, a3: T3, a4: T4) => R, args: [T1, T2, T3, T4], thisArg?: any): R;
function apply<T1, T2, T3, R>(f: (a1: T1, a2: T2, a3: T3) => R, args: [T1, T2, T3], thisArg?: any): R;
function apply<T1, T2, R>(f: (a1: T1, a2: T2) => R, args: [T1, T2], thisArg?: any): R;
function apply<T, R>(f: (...values: T[]) => R, args: T[], thisArg?: any): R;
function apply(f: any, args: any[], thisArg?: any) {
    return f.apply(thisArg, args);
}

Instead of bind, it was already easy enough to write curry* and bind* doing something like I did above, or the following:

function curry2<T1, T2, R>(f: (a1: T1, a2: T2) => R) {
    return (a1: T1) => (a2: T2) => f(a1, a2);
}
function curry3<T1, T2, T3, R>(f: (a1: T1, a2: T2, a3: T3) => R) {
    return (a1: T1) => (a2: T2) => (a3: T3) => f(a1, a2, a3);
}
// etc.
function bind2<T1, T2, R>(f: (a1: T1, a2: T2) => R, thisArg: any) {
    return (a1: T1) => (a2: T2): R => f.call(thisArg, a1, a2);
}
// etc.

call* could be typed in a similar manner.

@DanielRosenwasser Could you expand on why you are against my solution for bind? Do you believe your proposal will be better from a user standpoint?

As a typescript user, I want to use bind in an identical way to javascript. i.e.

myFunction.bind(undefined, arg0, arg1);

and have it typed as per my previous post. Your proposals would not support this. For example, using your solution 'bind2', the code would look like:

bind2(myFunction, undefined)(arg0);

And even worse for functions with more applied args e.g.

    bind5(myFunction, undefined)(arg0)(arg1)(arg2)(arg3);

Using bind as described in my proposal is not only more familiar to people who know javascript, but also allows easier transition from untyped javascript code into properly typed typescript.

@jameslong we generally understand what the desired behavior of bind is. Your solution is stating the cases which would be errors and which would not be but that's not really the level of detail holding back this feature. We need a solution that models the type signatures and type flow in a way that allows the compiler to actually figure out whether or not to report an error in these cases.

Thanks @danquirk, that effectively describes the main issue. The other is that I'm generally not a fan of having compiler-internal support of bind. We could do a lot of compiler-internal stuff, like type-checking static strings in an eval invocation. In _most_ cases, it's better to have a a fishing net than one really big fish (or something like that, I don't really know anything about fishing).

@jameslong, for your examples, why even bother with bind?

bind2(myFunction, undefined)(arg0)

becomes something like

(x: number) => myFunction(arg0, x)

and

bind5(myFunction, undefined)(arg0)(arg1)(arg2)(arg3)

becomes something like

(x: string) => myFunction(arg0, arg1, arg2, arg3, x)

It's not perfect, but it's not nearly the worst workaround either. If you need to specify a this argument, use the apply* functions I defined above.

@danquirk Thanks Dan. I appreciate there are complexities in the compiler implementation of this feature. However it seems there is not agreement on whether we would like this to be a compiler feature or not which is why I was making that point.

I think in general we'd all agree that any signature that has any in it would ideally have something more specific. There's certainly differing levels of priority of fixing such signatures depending on their relative use/complexity and the complexity of modeling them more exactly. Your list is certainly useful to have as a reference. At the moment we don't have a technical proposal that gets us far enough to even evaluate how it handles some cases in your list vs others.

apply and call could be easily fixed by variadic generic support (syntax only for demonstration, requires this typing).

interface Function {
  apply<T, R, ...X>(
    this: (this: T, ...args: X) => R,
    thisArg: T,
    args: X
  ): R;

  call<T, R, ...X>(
    this: (this: T, ...args: X) => R,
    thisArg: T,
    ...args: X
  ): R;
}

bind would be a beast to properly type, though, as a compiler would have to properly infer this kind of thing (syntax only for demonstration, requires this typing for the function version):

interface Function {
  // bound functions
  bind<T, R, ...X, ...Y>(
    this: (this: T, ...initial: X, ...rest: Y) => R,
    thisArg: T,
    ...initial: X
  ): (...rest: Y) => R;

  // bound constructors
  bind<C, ...X, ...Y>(
    this: new (...initial: X, ...rest: Y) => C,
    thisArg: any,
    ...initial: X
  ): new (...rest: Y) => C;
}

Let's just say it requires lazy inference of the template each use, probably using a simplified structure along the same lines as Conway's Game of Life or Rule 110, something not currently done, and something not easy to program.

let bar: T;
let baz: A;
let quux: B;
let spam: C;
let eggs: D;
declare function foo(this: T, a: A, b: B, c: C, d: D): E;

let res = foo.bind(bar, baz, quux);
res(spam, eggs)

_Expansion/substitution of last statement:_

// Initial
bind<T, R, ...X, ...Y>(this: (this: T, ...initial: X, ...rest: Y) => R, thisArg: T, ...initial: X): (this: any, ...rest: Y) => R;

// Steps to reduce
bind<T, R,       ...X,       ...Y>(this: (this: T,             ...initial: X,             ...rest: Y) => R, thisArg: T,             ...initial: X): (            ...rest: Y) => R;
bind<T, E,       ...X,       ...Y>(this: (this: T,             ...initial: X,             ...rest: Y) => E, thisArg: T,             ...initial: X): (            ...rest: Y) => E;
bind<T, E, A,    ...X,       ...Y>(this: (this: T, a: A,       ...initial: X,             ...rest: Y) => E, thisArg: T, a: A,       ...initial: X): (            ...rest: Y) => E;
bind<T, E, A, B, ...X,       ...Y>(this: (this: T, a: A, b: B, ...initial: X,             ...rest: Y) => E, thisArg: T, a: A, b: B, ...initial: X): (            ...rest: Y) => E;
bind<T, E, A, B,             ...Y>(this: (this: T, a: A, b: B,                            ...rest: Y) => E, thisArg: T, a: A, b: B               ): (            ...rest: Y) => E;
bind<T, E, A, B,       C,    ...Y>(this: (this: T, a: A, b: B,                c: C,       ...rest: Y) => E, thisArg: T, a: A, b: B               ): (c: C,       ...rest: Y) => E;
bind<T, E, A, B,       C, D, ...Y>(this: (this: T, a: A, b: B,                c: C, d: D, ...rest: Y) => E, thisArg: T, a: A, b: B               ): (c: C, d: D, ...rest: Y) => E;
bind<T, E, A, B,       C, D      >(this: (this: T, a: A, b: B,                c: C, d: D            ) => E, thisArg: T, a: A, b: B               ): (c: C, d: D            ) => E;

// Signature of what's finally type checked
bind<T, E, A, B, C, D>(this: (this: T, A, B, C, D) => E, T, A, B): (C, D) => E;

Not simple. (Not even C++ can do this without major template hacks)


_Or, in highly technical CS jargon, checking the above type correctly may mean controlling the variadic expansion in each occurence via a simple Class 1 cellular automaton, reducing it until the variadic types have been eliminated. There is the possibility that a template expands infinitely, especially if the function is recursive (singly or cooperatively), but a hard limit could prevent a lock-up for the compiler._

And as for this specific issue, it's nearly completely covered by the variadic generic proposal in #1773 and the this typing proposal in #229 and the large number of other dupes.

related to #3694

For those of you who care, I've create the following little TypeScript-snippet that outputs an overloaded function declaration of 'bind':

Output (you can scale this up if you want):

function bind<A, B, Z>(f: (_0: A, _1: B) => Z, _0: A): (_0: B) => Z;
function bind<A, B, C, Z>(f: (_0: A, _1: B, _2: C) => Z, _0: A): (_0: B, _1: C) => Z;
function bind<A, B, C, Z>(f: (_0: A, _1: B, _2: C) => Z, _0: A, _1: B): (_0: C) => Z;
function bind<A, B, C, D, Z>(f: (_0: A, _1: B, _2: C, _3: D) => Z, _0: A): (_0: B, _1: C, _2: D) => Z;
function bind<A, B, C, D, Z>(f: (_0: A, _1: B, _2: C, _3: D) => Z, _0: A, _1: B): (_0: C, _1: D) => Z;
function bind<A, B, C, D, Z>(f: (_0: A, _1: B, _2: C, _3: D) => Z, _0: A, _1: B, _2: C): (_0: D) => Z;
function bind<A, B, C, D, E, Z>(f: (_0: A, _1: B, _2: C, _3: D, _4: E) => Z, _0: A): (_0: B, _1: C, _2: D, _3: E) => Z;
function bind<A, B, C, D, E, Z>(f: (_0: A, _1: B, _2: C, _3: D, _4: E) => Z, _0: A, _1: B): (_0: C, _1: D, _2: E) => Z;
function bind<A, B, C, D, E, Z>(f: (_0: A, _1: B, _2: C, _3: D, _4: E) => Z, _0: A, _1: B, _2: C): (_0: D, _1: E) => Z;
function bind<A, B, C, D, E, Z>(f: (_0: A, _1: B, _2: C, _3: D, _4: E) => Z, _0: A, _1: B, _2: C, _3: D): (_0: E) => Z;
function bind(f, ...toBind: any[]) {
    return f.bind.apply(f, [null].concat(toBind));
}

Snippet:

function range(start: string,stop: string): string[] {
    var result: string[] = [];

    var index: number;
    for (var index = start.charCodeAt(0), end = stop.charCodeAt(0); index <= end; ++index){
        result.push(String.fromCharCode(index));
    }
    return result;
};

function untilIndex<T>(index: number, ts: T[]): T[] {
    return ts.slice(0, index);
}

function fromIndex<T>(index: number, ts: T[]): T[] {
    return ts.slice(index);
}

function getParameters(genericTypeNames: string[]): string[] {
    return genericTypeNames.map((genericTypeName, index) => {
        return '_' + index + ': ' + genericTypeName;
    });
}

function getParametersAsString(genericTypeNames: string[]): string {
    return getParameters(genericTypeNames).join(', ');
}

function getFunctionSignature(genericTypeNames: string[], returnTypeName: string): string {
    return '(' + getParametersAsString(genericTypeNames) + ') => ' + returnTypeName;
}

function getBindOverloads(genericTypeNames: string[]): string {

    var returnTypeName = 'Z';

    var functionToBindSignature: string = getFunctionSignature(genericTypeNames, returnTypeName);

    var overloads: string = '';

    var index: number;
    for (index = 1; index < genericTypeNames.length; ++index) {
        var before: string[] = untilIndex(index, genericTypeNames);
        var after: string[] = fromIndex(index, genericTypeNames);

        var hasBoundParameters: boolean = before.length > 0;
        var boundParametersAsString: string = getParametersAsString(before);
        var boundFunctionTypeAsString: string = getFunctionSignature(after, returnTypeName);

        overloads += 'function bind&lt;' + genericTypeNames.concat([returnTypeName]).join(', ') + '&gt;(' + 
            'f: ' + functionToBindSignature + 
            (hasBoundParameters ? (', ' + boundParametersAsString) : '') +
            '): '
            + boundFunctionTypeAsString
            + ';<br />'; 
    }

    return overloads;
}

function getAllBindOverloads(genericTypeNames: string[]): string {

    var result = '';

    var index: number;
    for (index = 0; index < genericTypeNames.length; ++index) {
        result += getBindOverloads(untilIndex(index, genericTypeNames));
    }

    return result + 'function bind(f, ...toBind: any[]) {<br />&nbsp;&nbsp;&nbsp;&nbsp;return f.bind.apply(f, [null].concat(toBind));<br />}';
}

document.write(getAllBindOverloads(range('A','F')));

Can this be approached in an incremental way? What I'm interested in the use of bind for, in most cases, is to avoid wrapping a callback in an arrow function.

interface AngularScope
{
   $watch<T>(event:string, callback: (newValue:T, oldValue:T)=>void);
}

class Example
{
    constructor($scope: AngularScope){
        $scope.$watch('change', this.theChangeCallback.bind(this));
    }

    theChangeCallback(new: string, old:string) {
        // Do work
    }
}

In this circumstance, I am not using bind for partial application. I am using it merely to avoid writing code that looks like this:

    $scope.$watch('change', (new:string, old:string)=>this.theChangeCallback(new, old));

In this situation, the type should be (new:string, old:string)=>void, just as theChangeCallback is in the class.

What would need to be added to the compiler, just to achieve this? I feel like if bind were able to take on the exact type of the function it's called from, it would be a start in the right direction. Partial application could be worked in later.

this.theChangeCallback

@bryanerayner you should use an arrow function to define the member in the class from the get go. This is covered here : https://basarat.gitbooks.io/typescript/content/docs/tips/bind.html

@bryanerayner my PR #6739 gives types to apply, call and bind. Take a look in core.d.ts: 5fe84781592a08b5294e01a2fbf42d1def07111d
I should improve the annotation to explicitly mention that this: void for the bound function.

this should be better now with https://github.com/Microsoft/TypeScript/pull/6739.

The ideal fix would be possible when/if https://github.com/Microsoft/TypeScript/issues/1773 is implemented.

@mhegazy isn't #6739 disabled (maybe only partially)? Its a bit confusing reading that + https://github.com/Microsoft/TypeScript/issues/7689 .

A clear message about what was added will help me read it better :rose:

6739 is merged in today. it should be available in typescript@next later tonight. #6739 adds two new features:

  • ability to define the type of this argument to a function or a method. it has to be the first argument in the list.
    the type specified will be checked when assigning and when calling the function.
  • a new flag --noImplicitThis, that makes it illegal to use this unless defined, either explicitly in a function, e.g function foo(this: Bar) { this } or implicitly, by virtue of being in a class.

Please see https://github.com/Microsoft/TypeScript/issues/6018 for more details.

The comments in https://github.com/Microsoft/TypeScript/issues/7689 are regarding a different proposal that #6739 implemented earlier to prevent tearing off methods of classes and interfaces, as there is an implicit this assumption about them. that was --strictThis or --strictThisChecks

@mhegazy thanks! :rose:

@mhegazy and @basarat thanks for bind, call and apply.
How about name: string (ES6)?

this is defined in https://github.com/Microsoft/TypeScript/blob/master/lib/lib.es6.d.ts#L4201, you get that automatically when you set --target ES6, or if you are using typescript@next, you can use --lib es6

@mhegazy I see, but I am using it in a polyfill for --target ES5 (to check if we're 2015-ready) ;) Basically no worries, I could "extend" the interface Function ...

if you are using a polyfill library, it should provide a .d.ts matching what it implements, see https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/core-js/core-js.d.ts for an example.

8354 reverts the change made by #6739 because it doesn't work correctly for overloaded functions:

declare function f(): number;
declare function f(s: string): string;

// BUT TypeScript can't use the number of arguments to determine which overload `call` should use.
var n: number = f.call(null); 
var s: string = f.call(null, 'foo');

The compiler currently compares the last overload in declaration order, so the return type inferred for f.call is _always_ string, never number.

mhegazy removed this from the TypeScript 2.0 milestone on Apr 28

Oh :(

Is this issue still on the radar?

We thought we had a fix in #6739 that solved it. but then that turned out to be invalid. please see @sandersn reply in https://github.com/Microsoft/TypeScript/issues/212#issuecomment-215488970 .

@mhegazy Does this mean the current state of overload resolution makes it too difficult to implement the feature, or can the work be salvaged? I'm trying to figure out if typed apply/bind/call is still on the table.

5453 needs to solve the overload problem for variadic kinds. At that point, variadic kinds plus this parameters should allow fully typed apply/bind/call. However, the prototype definitely doesn't address overload resolution and I don't think the proposal does either.

bind should return self type?

@frogcjn only if .bind is called with a single argument. If it's called with more than one, it changes the function signature.

Just FYI this is how it's handled by Flow 0.32.0:

function func(a: number, b: string) {
    console.log([a, b])
}

func.call(null, ['b'])

// 13: func.call(null, ['b'])
//     ^^^^^^^^^^^^^^^^^^^^^^ call of method `call`
// 13: func.call(null, ['b'])
//     ^^^^^^^^^^^^^^^^^^^^^^ undefined (too few arguments, expected default/rest parameters). This type is incompatible with
// 10: function func(a: number, b: string) {
//                                 ^^^^^^ string

func.call(null, ['b', 1])

// 13: func.call(null, ['b', 1])
//     ^^^^^^^^^^^^^^^^^^^^^^^^^ call of method `call`
// 13: func.call(null, ['b', 1])
//                     ^^^^^^^^ array literal. This type is incompatible with
// 10: function func(a: number, b: string) {
//                      ^^^^^^ number

FYI: This package is a workaround for this untyped bind() issue.
https://github.com/teppeis/bind.ts

bind

There was a simple bind overload by suggested by @jcalz at https://github.com/Microsoft/TypeScript/issues/16676#issuecomment-310128938 that would already improve the current situation a bit for when only the thisArg is provided:

interface Function {
  bind<F extends Function>(this: F, thisArg: any): F
}

For a more general solution, I tried a bind attempt at https://github.com/Microsoft/TypeScript/issues/5453#issuecomment-309035811, based on that and optionally #6606:

interface Function {
    bind<
        F extends (this: T, ...args: ArgsAsked) => R,
        ArgsAsked extends any[],
        R extends any,
        T,
        Args extends any[], // tie to ArgsAsked
        Left extends any[] = DifferenceTuples<ArgsAsked, Args>,
        EnsureArgsMatchAsked extends 0 = ((v: Args) => 0)(TupleFrom<ArgsAsked, TupleLength<Args>>)
        // ^ workaround to ensure we can tie `Args` to both the actual input params as well as to the desired params. it'd throw if the condition is not met.
    >(
        this: F,
        thisObject: T,
        ...args: Args
    ): (this: any, ...rest: Left) => R;
    // ^ `R` alt. to calc return type based on input (needs #6606): `F(this: T, ...[...Args, ...Left])`
}

@RyanCavanaugh:

Just a brief exploration of some of the issues here. We would need to fill in all the ?s along with descriptions of how those results would be achieved.

interface fn {
    (): string;
    (n: number): void;
    (n: number, m: number): Element;
    (k: string): Date;
}

var x: fn;
var y = x(); // y: string

// original version, where mutation screws everything up:
var args = [3]; // args: number[]
var c1 = x.apply(window, args); // error, not guaranteed to get a matching overload for `number[]`. had there been a `number[]` overload yielding say `Foo`, then probably infer `string | void | Element | Foo`?
args = []; // arg: still number[]
var c2 = x.apply(window, args); // c2: ditto

// without mutation, the only way around it I can see:
const args1 = [3]; // args1 type: [3], following https://github.com/Microsoft/TypeScript/issues/16389
var c1 = x.apply(window, args1); // c1: void
const args2 = []; // args2 type: for this to work this would have to be give the empty tuple type []. is it even possible to expose that?
var c2 = x.apply(window, args2); // c2: string

var b = x.bind(undefined, null); // b type: normally `(...args: Args) => fn(null, ...Args)` based on 5453 + 6606, but with `strictNullChecks` error since having `null` as the first argument would already result in no matching overloads in `fn`.
var j = b(); // j: with `strictNullChecks`: `any` since that's what `b` got due to its error. without `strictNullChecks`: `void`, as it'd just settle for the first unary overload.

@mhegazy Any updates this. Need this for typescript to support currying, partial applications etc. in functional programming.

I wanted these to be strongly typed and found out that it's relatively easy to type, just time consuming. As a result, I made a generator to do it for me. People might have done this previously but I couldn't find anything. Was a fun side project.

Generated types (supports up to 10 arguments): https://gist.github.com/Yuudaari/f4c21f8e5e3dad36e4c7f61cbddb4f22
Generator (for any number of arguments... it's up to you!): https://gist.github.com/Yuudaari/508709f582c4aa76eeea70365d145c93

Replace native methods with ones that support these methods on class constructors: https://gist.github.com/Yuudaari/68662657485524c8f1c65250e7c2d31f
This uses prototype but you can pretty easily make it not if you so desire.

I really wish there was syntactic sugar for bind already tho... Even tho it's strongly typed, using bind still feels a bit hacky/like overkill

@Yuudaari looks like a good candidate for lib.d.ts :)

@RyanCavanaugh Any interest in merging in massively overloaded typings for apply, bind, and call (like what @Yuudaari wrote up) as an improvement until variadic types are in?

@bcherny There are caveats to it; using the methods on varargs functions falls back to the provided function overloads, currently, whereas if there were just my overloads, it would not work. This also means that it can't be used for type checking, really, because the original is vague so it'll accept anything that doesn't work w/ my overloads. My overloads are only really nice for a little bit of type hinting. I'm not sure they're production ready. Also, there might be a non-negligible performance impact of adding a lot of overloads, but I haven't compared with and without.

It's also not that hard to just stick them in your projects if you want them.

@yuudaari Not sure I totally understand. Could you comment with a few examples of what works, and a few of what doesn’t?

In case folks aren't aware, the tuple rest/spread work in #24897 slated for TS 3.0 will probably end up fixing this issue in the not-too-distant future.

@ahejlsberg said:

Strong typing of bind, call, and apply

With this PR it becomes possible to strongly type the bind, call, and apply methods on function objects. This is however a breaking change for some existing code so we need to investigate the repercussions.

@jcalz to qualify that, #24897:

does:

  • help capture params, needed for its example bind(f3, 42); // (y: string, z: boolean) => void

does not:

  • help manipulate tuples, needed to extend bind to an arbitrary number of arguments without ugly hacks just to get from [A, B] to [A, B, C] or the other way around. the good news is apply and call should not need this, though bind, curry and compose do.
  • correct return types when manipulating functions with generics/overloads (-> #6606)

That said, it's a great step forward.

@tycho01 very interesting. Do we know of any plans or PRs that enable us to have a fully strong-typed bind with an arbitrary amount of parameters?

Also, @ahejlsberg said that "we need to investigate the repercussions". Do we have a timeline on when this will happen, or an issue that keeps track of this progress?

@ffMathy:

repercussions: timeline/issue?

dunno, ask him in there!

Do we know of any plans or PRs that enable us to have a fully strong-typed bind with an arbitrary amount of parameters?

I think the non-hacky tuple Concat alternative ([...T]) needed for bind is what remains of #5453 now after #24897.

Now implemented in #27028.

Was this page helpful?
0 / 5 - 0 ratings