Когда TypeScript использует структурную типизацию? - PullRequest
3 голосов
/ 09 ноября 2019

Учитывая приведенные ниже типы, почему компилятор разрешает присваивание ниже? Я предполагаю, что это связано с TypeScript, использующим структурную типизацию в некоторых ситуациях (то есть, поскольку Success и Failure структурно эквивалентны, компилятор рассматривает их как взаимозаменяемые), но я предполагаю, что мне не ясно, при каких условиях структурнаяпечатать использовал.

////////////////////
// Types
////////////////////

class Success<S> {
    constructor(public value: S) { }
    static create<S, F>(value: S):Result<S, F> {
        return new Success<S>(value);
    }
}

class Failure<F> {
    constructor(public value: F) {}
    static create<S, F>(value: F): Result<S, F> {
        return new Failure<F>(value);
    }
}

type Result<S, F> = Success<S> | Failure<F>;


////////////////////
// Usage
////////////////////

/* 
How is the assignment below allowed by the compiler? 
Failure.create produces a Result<unknown, number> which should not
be assignable to a Result<number, Error>
*/

const f: Result<number, Error> = Failure.create(2);

ДОПОЛНИТЕЛЬНЫЙ КОНТЕКСТ ДЛЯ ЛЮБИМОГО: Как указывает Мальволио, проблема заключается в том, что объединения типов являются коммутативными, а способ обхода состоит в том, чтобы типы не были структурно эквивалентными. Решение Малаволио состоит в том, чтобы дать типам различные поля (например, svalue и fvalue). Это работает, но я предпочитаю, чтобы интерфейс был таким же, поэтому я выбрал решение ниже, используя символы, чтобы различать классы (Если это связано с проблемами, пожалуйста, включите):

export const SuccessType = Symbol();
export class Success<S> {
    public readonly resultType = SuccessType;
    private constructor(public value: S) {}
    static create<S, F>(value: S): Result<S, F> {
        return new Success<S>(value);
    }
}

export const FailureType = Symbol();
export class Failure<F> {
    public readonly resultType = FailureType;
    private constructor(public value: F) {}
    static create<S, F>(value: F): Result<S, F> {
        return new Failure<F>(value);
    }
}

1 Ответ

2 голосов
/ 09 ноября 2019

Ничего себе. Это очень тонко, но попробуйте это вместо этого:

class Success<S> {
    constructor(public svalue: S) { }
    static create<S, F>(value: S):Result<S, F> {
        return new Success<S>(value);
    }
}

class Failure<F> {
    constructor(public fvalue: F) {}
    static create<S, F>(value: F): Result<S, F> {
        return new Failure<F>(value);
    }
}

type Result<S, F> = Success<S> | Failure<F>;


const f: Result<number, Error> = Failure.create(2);

Ваш Result<S, F>, по определению, это все, что имеет свойство value с типом S | F. Следовательно, Result<S, F> присваивается Result<F, S> - объединение типов является коммутативным.

В моей версии Result<S, F> - это то, что имеет либо свойство svalue с типом S, либо свойствоfvalue с типом F, поэтому Result<S, F> нельзя присвоить Result<F, S>

...