Home > Mobile >  Wrapping a Function with Generics
Wrapping a Function with Generics

Time:12-09

I'm attempting to create a higher-order function that wraps inputted callbacks with a block of code and returns it. I want the returned function to have the same parameters, but a slightly different return value.

I've already tried something like this:

const wrapper = <Callback extends (...args: any) => any>(fn: Callback) => {
    return (...newFnArgs: Parameters<typeof fn>) => {
        try {
            const val = fn(...newFnArgs as any /* lazy type cast */)
            return {val} as {val: ReturnType<Callback>}
        } catch {
            return {val: null}
        } 
    }     
}

// works fine
const concreteFuntion = (n: number) => n
// inferred as (n: number) => {val: number} | {val: null}
const wrappedConcrete = wrapper(concreteFuntion)

// with generics doesn't work
const genericFunction: <T>(a: T) => T = (n) => n
// why is generic inferred as "unknown"
// inferred as (n: unknown) => {val: unknown} | {val: null}
const wrappedGeneric = wrapper(genericFunction)

but unfortunately whenever a function has a generic, the generic is inferred as unknown. Why is this happening? Is there any way to allow this to work with generic functions too?

CodePudding user response:

I honestly don't see the point all the gymnastics you're getting into with Parameters, ReturnType, explicit casts, etc. Why not just make the wrap() function generic with a type parameter for the arguments and a type parameter for the return type?

const wrap = <A extends unknown[], R>(
    fn: (...args: A) => R
): (...args: A) => { val: R | null} => {
    return (...args: A) => {
        try { return { val: fn(...args) }; }
        catch { return { val: null }; }
    }
};

const concreteFuntion = (n: number, s: string) => n   s;
const wrappedConcrete = wrap(concreteFuntion);
// ↑ inferred as (n: number. s: string) => { val: string | null; }

const genericFunction: <T>(a: T) => T = (n) => n;
const wrappedGeneric = wrap(genericFunction);
// ↑ inferred as <T>(a: T) => { val: T | null; }

Playground link

  • Related