Develop branch: proper way to specify callback function parameter with a specific return type

Topics: General, Language Specification
Oct 19, 2013 at 7:48 PM
Just tried updating to the latest 'develop' branch and I'm noticing that all calls to functions declared like this are now breaking (this is from underscore.d.ts):
    export function memoize(
        fn: Function,
        hashFn?: (...args: any[]) => string): Function;
I'm now seeing error messages like this:
Supplied parameters do not match any signature of call target:
    Call signatures of types '(from: any, to: any) => string' and '(...args: any[]) => string' are incompatible:
        Call signature expects 0 or fewer parameters.
The commonality in all these new errors seems to be a callback function with a specific return type but arbitrary arguments. This pattern is used in underscore, backbone, etc...

What is the new proper way to specify a callback function like this with the 'develop' compiler?
Developer
Oct 21, 2013 at 6:28 PM
Edited Oct 21, 2013 at 6:45 PM
Because rest parameters are equivalent to an infinite expansion of optional parameters the arguments for callbacks passed as implementations for this parameter type must also be optional. It's a little weird but optional params in callbacks are actually more restrictive than non-optional params, see https://typescript.codeplex.com/discussions/428458. It may or may not be preferable to have some number of overloads where the callbacks have 1-n non-optional parameters.
Oct 21, 2013 at 7:40 PM
Ok, thanks for the explanation - I think I understand what you are saying. So it sounds like the only alternative for the example above is to discard the information about the return type and just specify 'Function' since we don't know what the args are ahead of time:
    export function memoize(
        fn: Function,
        hashFn?: Function): Function;
Is there any consideration of making Function generic on its return type? Then we could do something like this:
    export function memoize<T>(
        fn: Function<T>,
        hashFn?: Function<string>): Function<T>;
Developer
Oct 21, 2013 at 9:04 PM
You should be able to do something like this yourself with a generic type signature for memoize. For example:
declare function memoize<T>(x: Func<T>): Func<T>;

interface Func<T> {
    (x: T): T;
}

function doStuff(x: number) {
    return x;
}
var m = memoize(doStuff);
var r = m(2); // r is number
Does that do what you want? I assume you would just prefer not to update all of underscore, backbone, etc to generic .d.ts files :)
Oct 21, 2013 at 10:04 PM
I'm not sure that does what I need. The problem is that we don't know the input parameter(s) of the function ahead of time. I was hoping something like this would work but it has the same issue:
    export function memoize<T>(
        fn: GenericFunction<T>,
        hashFn?: GenericFunction<string>): GenericFunction<T>;

    interface GenericFunction<T> {
        (...args: any[]): T;
    }
Developer
Oct 22, 2013 at 1:47 PM
Probably the best you can do for Underscore's memoize function is:
declare function memoize<T extends Function>(fn: T, hashFn?: Function): T;
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.
Oct 22, 2013 at 9:34 PM
Ah yeah, good idea - thanks. Ok, I'll update the definition files on DefinitelyTyped once the big regression from today is fixed so that I can verify the fix (my latest comment on http://typescript.codeplex.com/workitem/1807 talks about the new regression from today).