Подмножества и подтипы
Что такое подтип?
Тип, такой как number, boolean или string, описывает множество возможных значений. number описывает все возможные числа, поэтому отдельное число (например, 42) будет подтипом типа number. И наоборот, number будет надтипом типа 42.
Чтобы определить, является ли один тип подтипом другого, нужно рассмотреть все возможные значения обоих типов и выяснить, является ли множество значений одного из них подмножеством множества значений другого.
Например, если у нас есть TypeA, описывающий числа от 1 до 3 (объединение литеральных типов), и TypeB, описывающий числа от 1 до 5, то TypeA будет считаться подтипом TypeB, поскольку TypeA является подмножеством TypeB.
type TypeA = 1 | 2 | 3; type TypeB = 1 | 2 | 3 | 4 | 5;
Рассмотрим TypeLetters, описывающий строки «A», «B», «C», и TypeNumbers, описывающий числа 1, 2, 3. Ни один из них не будет подтипом другого, поскольку каждый содержит совершенно отдельный набор значений.
type TypeLetters = "A" | "B" | "C"; type TypeNumbers = 1 | 2 | 3;
Наконец, если у нас есть TypeA, описывающий числа от 1 до 3, и TypeB, описывающий числа от 3 до 5, ни один из них не будет подтипом другого. Хотя оба содержат число 3 и описывают числа, каждый из них включает некоторые уникальные элементы.
type TypeA = 1 | 2 | 3; type TypeB = 3 | 4 | 5;
Когда используются подтипы?
Большая часть работы Flow заключается в сравнении типов друг с другом.
Например, чтобы определить, правильно ли вы вызываете функцию, Flow должен сравнить передаваемые вами аргументы с параметрами, которые ожидает функция.
Часто для этого нужно выяснить, является ли передаваемое значение подтипом ожидаемого значения.
Поэтому, если вы напишете функцию, ожидающую числа от 1 до 5, ей подойдёт любой подтип этого множества.
function f(param: 1 | 2 | 3 | 4 | 5) {
// ...
}
declare const oneOrTwo: 1 | 2; // Subset of the input parameters type.
declare const fiveOrSix: 5 | 6; // Not a subset of the input parameters type.
f(oneOrTwo); // Works!
f(fiveOrSix); // Error! Подтипы сложных типов
Flow должен сравнивать не только множества примитивных значений: ему также необходимо уметь сравнивать объекты, функции и все остальные типы, используемые в языке.
Подтипы объектов
Для начала можно сравнить два объекта по их ключам. Если объект содержит все ключи другого объекта, он может быть его подтипом.
Например, если у нас есть ObjectA с ключом foo и ObjectB с ключами foo и bar, то ObjectB может быть подтипом ObjectA, если ObjectA является неточным.
type ObjectA = {foo: string, ...};
type ObjectB = {foo: string, bar: number};
let objectB: ObjectB = {foo: 'test', bar: 42};
let objectA: ObjectA = objectB; // Works! Но также нужно сравнить типы значений. Если у обоих объектов есть ключ foo, но один имеет тип number, а другой — string, то ни один из них не будет подтипом другого.
type ObjectA = {foo: string, ...};
type ObjectB = {foo: number, bar: number};
let objectB: ObjectB = { foo: 1, bar: 2 };
let objectA: ObjectA = objectB; // Error! Если эти значения объекта сами являются объектами, их нужно будет сравнить друг с другом. Необходимо рекурсивно сравнивать каждое значение, пока не станет ясно, является ли один тип подтипом другого.
Подтипы функций
Правила подтипизации функций сложнее. До сих пор мы видели, что A является подтипом B, если B содержит все возможные значения для A. Для функций неясно, как применить эту взаимосвязь. Для простоты можно считать, что тип функции A является подтипом типа функции B, если функции типа A можно использовать везде, где ожидается функция типа B.
Допустим, у нас есть тип функции и несколько функций. Какие из этих функций можно безопасно использовать в коде, который ожидает функцию заданного типа?
type FuncType = (1 | 2) => "A" | "B"; declare function f1(1 | 2): "A" | "B" | "C"; declare function f2(1 | null): "A" | "B"; declare function f3(1 | 2 | 3): "A"; f1 as FuncType; // Error f2 as FuncType; // Error f3 as FuncType; // Works!
-
f1может возвращать значение, котороеFuncTypeникогда не возвращает, поэтому код, рассчитанный наFuncType, может работать небезопасно, если использоватьf1. Её тип не является подтипомFuncType. -
f2не может обработать все значения аргументов, которые обрабатываетFuncType, поэтому в коде, рассчитанном наFuncType, нельзя безопасно использоватьf2. Её тип также не является подтипомFuncType. -
f3может принимать все значения аргументов, которые принимаетFuncType, и возвращает только те значения, которые возвращаетFuncType, поэтому её тип является подтипомFuncType.
В общем случае правило подтипизации функций таково: тип функции B является подтипом типа функции A тогда и только тогда, когда входные значения B образуют надмножество входных значений A, а выходные значения B — подмножество выходных значений A. Подтип должен принимать как минимум те же входные значения, что и родительский тип, и возвращать не более того же набора выходных значений.
Направление применения правила подтипизации к входным и выходным значениям определяется вариантностью, которой посвящён следующий раздел.
См. также
- Вариантность — взаимодействие подтипизации с параметрами обобщённых типов
- Иерархия типов — наглядное представление взаимосвязей всех типов
- Номинальная и структурная типизация — определение отношений подтипов с помощью классов и объектов
© 2013–present Facebook Inc.
Licensed under the MIT License.
https://flow.org/en/docs/lang/subtypes