You may know the functions bind and apply (and call, but it’s basically the same as apply and I don’t use it anyway) from javascript.

The idea behind those function is that for an object o with a method f we have o.f(a) equivalent to o.f.apply(o, [a]) or o.f.bind(o)(a).

The problem is that their respective typings made for Typescript are too permissive for my own taste. They allow too much.

class Plane {
    flyTo(lat: number, lon: number): string {
        return "flying to " + lat + ", " + lon;
    }
}

let o: Object = new Object();
let p: Plane = new Plane();

p.flyTo.apply(p, [1, 2]);
p.flyTo.apply(o, [1, 2]); // I don't want that to typecheck! o is not a plane.

For reference here are the typings for those functions in the typescript library (you can skip them if you want):

interface Function {
    apply(this: Function, thisArg: any, argArray?: any): any;

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

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

    // ...
}

interface CallableFunction extends Function {
    apply<T, R>(this: (this: T) => R, thisArg: T): R;
    apply<T, A extends any[], R>(this: (this: T, ...args: A) => R, thisArg: T, args: A): R;

    call<T, A extends any[], R>(this: (this: T, ...args: A) => R, thisArg: T, ...args: A): R;

    bind<T>(this: T, thisArg: ThisParameterType<T>): OmitThisParameter<T>;
    bind<T, A0, A extends any[], R>(this: (this: T, arg0: A0, ...args: A) => R, thisArg: T, arg0: A0): (...args: A) => R;
    bind<T, A0, A1, A extends any[], R>(this: (this: T, arg0: A0, arg1: A1, ...args: A) => R, thisArg: T, arg0: A0, arg1: A1): (...args: A) => R;
    bind<T, A0, A1, A2, A extends any[], R>(this: (this: T, arg0: A0, arg1: A1, arg2: A2, ...args: A) => R, thisArg: T, arg0: A0, arg1: A1, arg2: A2): (...args: A) => R;
    bind<T, A0, A1, A2, A3, A extends any[], R>(this: (this: T, arg0: A0, arg1: A1, arg2: A2, arg3: A3, ...args: A) => R, thisArg: T, arg0: A0, arg1: A1, arg2: A2, arg3: A3): (...args: A) => R;
    bind<T, AX, R>(this: (this: T, ...args: AX[]) => R, thisArg: T, ...args: AX[]): (...args: AX[]) => R;
}

interface NewableFunction extends Function {
    apply<T>(this: new () => T, thisArg: T): void;
    apply<T, A extends any[]>(this: new (...args: A) => T, thisArg: T, args: A): void;

    call<T, A extends any[]>(this: new (...args: A) => T, thisArg: T, ...args: A): void;

    bind<T>(this: T, thisArg: any): T;
    bind<A0, A extends any[], R>(this: new (arg0: A0, ...args: A) => R, thisArg: any, arg0: A0): new (...args: A) => R;
    bind<A0, A1, A extends any[], R>(this: new (arg0: A0, arg1: A1, ...args: A) => R, thisArg: any, arg0: A0, arg1: A1): new (...args: A) => R;
    bind<A0, A1, A2, A extends any[], R>(this: new (arg0: A0, arg1: A1, arg2: A2, ...args: A) => R, thisArg: any, arg0: A0, arg1: A1, arg2: A2): new (...args: A) => R;
    bind<A0, A1, A2, A3, A extends any[], R>(this: new (arg0: A0, arg1: A1, arg2: A2, arg3: A3, ...args: A) => R, thisArg: any, arg0: A0, arg1: A1, arg2: A2, arg3: A3): new (...args: A) => R;
    bind<AX, R>(this: new (...args: AX[]) => R, thisArg: any, ...args: AX[]): new (...args: AX[]) => R;
}

I wanted to add my own definitions to the typescript standard library but I’m not a fan of editing a code that could be erased with an update so I made wrappers. The first iteration was:

function safeApply<T extends (...args: any) => any>(fun: T, thisArg: ThisParameterType<T>, args: Parameters<T>): ReturnType<T> {
    return fun.apply(thisArg, args);
}

function safeBind<T extends (...args: any) => any>(fun: T, thisArg: ThisParameterType<T>): (...args: Parameters<T>) => ReturnType<T>{
    return fun.bind(thisArg);
}

For those who don’t know ThisParameterType, Parameters and ReturnType all comes from the utility types provided by typescript:

/**
 * Obtain the parameters of a function type in a tuple
 */
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

/**
 * Obtain the return type of a function type
 */
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

/**
 * Extracts the type of the 'this' parameter of a function type, or 'unknown' if the function type has no 'this' parameter.
 */
type ThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any ? U : unknown;

The syntax is weird, it uses conditional-types which are conditions evaluated at type checking time. The doc says:

A conditional type selects one of two possible types based on a condition expressed as a type relationship test:

T extends U ? X : Y

The type above means when T is assignable to U the type is X, otherwise the type is Y.

So type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never; would read something like The type Parameter<T>, which is only valid if T is a function ((...args: any) => any is a function), is either:

  • P if T is a function with the list of arguments is of type P
  • never otherwise
// Basically this means that all of this is correct
let par: Parameters<Plane["flyTo"]> = [1, 2]; // because the arguments of the flyTo method are [number, number]
let ret: ReturnType<Plane["flyTo"]> = "some string"; // because flyTo returns a string
let thi: ThisParameterType<Plane["flyTo"]> = undefined; // because ... well, I don't really know, it should be Plane, we are going to fix that

Why does ThisParameterType<Plane["flyTo"]> evaluates to undefined? Apparently it’s because typescript need to be said what is the type of this in the function. Like that:

class Plane {
    flyTo(lat: number, lon: number): string {
        return "flying to " + lat + ", " + lon;
    }

    flyToWithThis(this: Plane, lat: number, lon: number): string {
        return "flying to " + lat + ", " + lon;
    }
}

function() {
    let o: Object = new Object();
    let p: Plane = new Plane();

    safeApply(p.flyToWithThis, p, [1, 2]); // this works, so far so good
    safeApply(p.flyToWithThis, o, [1, 2]); // and this gives an error! Finally!
    /*
    Argument of type 'Object' is not assignable to parameter of type 'Plane'.
        The 'Object' type is assignable to very few other types. Did you mean to use the 'any' type instead?
            Type 'Object' is missing the following properties from type 'Plane': flyTo, flyToWithThists(2345)
    */

    let thi: ThisParameterType<Plane["flyToWithThis"]> = new Plane(); // no surprise here

Ok, so I have to put the this: type thingy in every function that I plan to call apply (or bind) on. Ok, I’ll do it. But what if I forgot? What if, at one point, I call bind and forgot to put the this: type thingy in here, it would be nice if typescript would give me a warning. Because safeApply(p.flyTo, o, [1, 2]); still typecheck.

The problem is that ThisParameterType returns unknown when there is no this: type thingy, and an unknown parameter can be assigned anything (which is completly normal), so let’s change that:

type     ThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any ? U                                                             : unknown;
type SafeThisParameterType<T> = T extends (this: unknown, ...args: any[]) => any ? never : (T extends (this: infer U, ...args: any[]) => any ? U : never );

So SafeThisParameterType<T> is never unless T has a this: type thingy, in which case it is the type of the list of arguments taken by T (T is still a function).

And safeApply and safeBind become:

function safeApply<T extends (...args: any) => any>(fun: T, thisArg: SafeThisParameterType<T>, args: Parameters<T>): ReturnType<T> {
    return fun.apply(thisArg, args);
}

function safeBind<T extends (...args: any) => any>(fun: T, thisArg: SafeThisParameterType<T>): (...args: Parameters<T>) => ReturnType<T>{
    return fun.bind(thisArg);
}
function() {
    let o: Object = new Object();
    let p: Plane = new Plane();

    safeApply(p.flyToWithThis, p, [1, 2]); // still works, good
    safeApply(p.flyToWithThis, o, [1, 2]); // still an error, still good
    /*
    Argument of type 'Object' is not assignable to parameter of type 'Plane'.
        The 'Object' type is assignable to very few other types. Did you mean to use the 'any' type instead?
            Type 'Object' is missing the following properties from type 'Plane': flyTo, flyToWithThists(2345)
    */

    safeApply(p.flyTo, p, [1, 2]); // gives an error! Yay! I won't forgot to add this: type
    // Argument of type 'Plane' is not assignable to parameter of type 'never'.ts(2345)

All is good.

Obviously there is weird cases where it would make sense to use bind/call in a situation where safeBind/safeCall gives an error, in which case you are free to use them if you know what you’re doing. I often don’t.