Create object of mapped type with a constraint

喜夏-厌秋 提交于 2021-01-29 05:43:57

问题


Similar to this question I'm trying to create an object of a mapped type. However, I keep getting type errors in the implementation.

Here's a toy example:

type SingleArgFunction<A> = (x: A) => A;

type ArrayedReturnFunction<A> = (x: A) => A[];

// create an arrayed function from a single arg function

type MakeArrayed<F> = F extends SingleArgFunction<infer A> ? ArrayedReturnFunction<A> : never;

// mapped type

type MakeArrayedAll<FS> = {
    [K in keyof FS]: MakeArrayed<FS[K]>
}

// example usage

type MyFunctions = {
    foo: SingleArgFunction<number>,
    bar: SingleArgFunction<string>
}

type MyArrayedFunctions = MakeArrayedAll<MyFunctions> // ok

// functions to convert an object of single arg functions to object of arrayed functions

interface SingleArgFunctionObject {
    [key: string]: SingleArgFunction<any>
}

function makeArrayed<A>(f: SingleArgFunction<A>): ArrayedReturnFunction<A> {
    return function (x) {
        return [f(x)];
    }
}

function makeArrayedAll<FS extends SingleArgFunctionObject>(fs: FS): MakeArrayedAll<FS> {
    const keys = Object.keys(fs) as (keyof FS)[];
    const result = {} as MakeArrayedAll<FS>;

    for (let key of keys) {
        result[key] = makeArrayed(fs[key]);
    }

    return result;
}

This gives the following type error for result:

const result: MakeArrayedAll<FS>
Type 'ArrayedReturnFunction<any>' is not assignable to type 'MakeArrayed<FS[keyof FS]>'.(2322)

The difference with the previous question is that the properties of the unmapped type FS need to be constrained via FS extends SingleArgFunctionObject. Is there a solution other than asserting result as any?

Playground


回答1:


I'd be inclined to refactor your types so as to avoid conditional types (which are hard for the compiler to reason about) and instead use mapped types and inference from mapped types as much as possible. Your makeArrayed() function looks fine to me as-is.

I'll define SingleArgFunctionObject and ArrayedReturnFunctionObject to be generic in the object type AS that it maps over... the idea is that each property of AS is treated like the A in your SingleArgFunction<A> and ArrayedReturnFunction<A>:

type SingleArgFunctionObject<AS extends object> = {
    [K in keyof AS]: SingleArgFunction<AS[K]>
}

type ArrayedReturnFunctionObject<AS extends object> = {
    [K in keyof AS]: ArrayedReturnFunction<AS[K]>
}

Armed with this, makeArrayedAll() will be generic in AS. The tricky bit for getting the compiler not to complain is to make the for loop treat each key as a generic K extends keyof AS. That's easier to do with the forEach() method of arrays:

function makeArrayedAll<AS extends object>(
    fs: SingleArgFunctionObject<AS>
): ArrayedReturnFunctionObject<AS> {
    const result = {} as ArrayedReturnFunctionObject<AS>;
    (Object.keys(fs) as (keyof AS)[]).forEach(<K extends keyof AS>(key: K) => {
        result[key] = makeArrayed(fs[key]);
    })
    return result;
}

That should produce the same result as your code, but now the compiler is more confident that makeArrayed(fs[key]) of type ArrayedReturnFunction<AS[K]> is assignable to result[key] of type ArrayedReturnFunctionObject<AS>[K].

Okay, hope that helps; good luck!

Playground link



来源:https://stackoverflow.com/questions/59895071/create-object-of-mapped-type-with-a-constraint

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!