Вывод общего типа «этот»

Следуя этому вопросу, теперь я пытаюсь создать функции с явным этот параметр типа, расширяющего интерфейс IModel:

// interface for Model class

interface IModel {
    state: {}
}

// based on answer to this question https://stackoverflow.com/q/59895071/374328
// now functions will be bound to model instance, so need to specify 'this' parameter as Model

type SingleArgFunction<Model extends IModel, A> = (this: Model, x: A) => A;
type ArrayedReturnFunction<Model extends IModel, A> = (this: Model, x: A) => A[];

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

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

function makeArrayed<Model extends IModel, A>(f: SingleArgFunction<Model, A>): ArrayedReturnFunction<Model, A> {
    return function (x) {
        return [f.call(this, x)];
    }
}

function makeArrayedAll<Model extends IModel, AS extends object>(
    fs: SingleArgFunctionObject<Model, AS>
): ArrayedReturnFunctionObject<Model, AS> {
    const result = {} as ArrayedReturnFunctionObject<Model, AS>;

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

Пример определения модели и типа:

interface MyModel extends IModel {
    state: {
        x: number;
    }
}

interface SingleArgFunctions {
    foo: SingleArgFunction<MyModel, number>
}

interface ArrayedReturnFunctions {
    foo: ArrayedReturnFunction<MyModel, number>;
}

Создание объекта ArrayedReturnFunctions напрямую допустимо, а this выводится как тип MyModel:

const arrayedReturnFunctions1: ArrayedReturnFunctions = {
    foo(x) {
        return [x + this.state.x]; // ok
    }
}

Кроме того, создание объекта путем применения makeArrayed к одной функции также допустимо:

const arrayedReturnFunctions2: ArrayedReturnFunctions = {
    foo: makeArrayed(function (x) {
        return x + this.state.x; // ok
    })
}

Однако использование makeArrayedAll не работает — this выводится как тип IModel:

const arrayedReturnFunctions3: ArrayedReturnFunctions = makeArrayedAll({
    foo(x) {
        return x + this.state.x; // error - property x does not exist on type {}
    }
})

Даже создание объекта типа SingleArgFunctions и последующая передача его в makeArrayedAll не работает:

const singleArgFunctions: SingleArgFunctions = {
    foo(x) {
        return this.state.x + x;
    }
}

const arrayedReturnFunctions4 = makeArrayedAll(singleArgFunctions); // error - IModel is not assignable to type MyModel 

Почему тип Model не выводится как MyModel при использовании makeArrayedAll?

Playground


person stw    schedule 26.01.2020    source источник


Ответы (1)


Для меня это выглядит так, как будто вы надеетесь, что тип переменной arrayedReturnFunctions, которой вы назначаете вывод makeArrayedAll(), обеспечит достаточную контекстную типизацию для ввода makeArrayedAll(), чтобы контекст this методов ввода был выведен... но этого не происходит. Я не совсем удивлен, что контекстуальный вывод не может произойти в этом сценарии, но у меня нет хорошего ответа о том, почему именно или как заставить это произойти.

Прямо сейчас мое единственное предложение состоит в том, чтобы отметить, что вывод типов имеет тенденцию работать лучше в «прямом» направлении; то есть параметры типа универсальной функции легче вывести из ввода этой функции, а не из ожидаемого типа вывода функции. И если это не удается, вы всегда можете вручную указать параметр универсального типа.

Вот один из способов вручную указать параметр типа модели и позволить компилятору сделать вывод об остальном, используя каррирование :

const makeArrayedAllFor = <M extends IModel>() => <AS extends object>(
  fs: SingleArgFunctionObject<M, AS>) => makeArrayedAll(fs);

const arrayedReturnFunctions3: ArrayedReturnFunctions = makeArrayedAllFor<MyModel>()({
    foo(x) {
        return x + this.state.x;
    }
})

const arrayedReturnFunctions4 = makeArrayedAllFor<MyModel>()(singleArgFunctions);

Теперь это работает, хотя и громоздко. Это лучшее, что я могу придумать на данный момент; может у кого есть другие идеи? О, удачи!

playground link

person jcalz    schedule 26.01.2020
comment
Мое намерение использовать это заключалось в том, чтобы вывод типа происходил из ожидаемого вывода функции, и он отлично работает во втором примере, но не в третьем. Вывод типа в четвертом примере выполняется в прямом направлении, но все равно не удается. Ваше решение действительно работоспособно - спасибо. Я оставлю вопрос открытым на некоторое время, чтобы посмотреть, есть ли какие-либо другие идеи. - person stw; 26.01.2020