TypeScript ã® Narrowing
ã¯ããã«
ãã®èšäºã§ã¯ãWidening(åã®æ¡å€§) ã®å¯Ÿãšãªã Narrowing(åã®çµã蟌ã¿) ã«ã€ããŠè§£èª¬ããŸãã
Narrowing ã¯å€ãã®èšäºã解説ã«ãã㊠åã¬ãŒã (type guard) ãšåŒã°ããçšèªã«åºã¥ããŠè§£èª¬ãããŸãããNarrowing ã®ããŒã¯ãŒãã§å æ¬çã«è§£èª¬ããã®ãå ¬åŒããã¥ã¡ã³ãã§ãè¡ãããŠããããæ¹ã§ãã
å®éãåã¬ãŒãããã察æŠå¿µã§ãããWidening(åã®æ¡å€§)ãããåã®éåæ§ããªã©ãå ã㊠Narrowing ãšããŠèããæ¹ãããããã«ã€ããŠããã¹ãããªãšç解ããããšãå¯èœã«ãªããŸã (ç¹ã«å€å®å¯èœãªãŠããªã³åãªã©ã«ã€ããŠã¯ããã§ã)ã
åéå
ãããããšãªããŸãã ååã®èšäº ã§ã¯ãåã¯ä»¥äžã®å³ (fig 1) ã®ããã«å
·äœçãªå€ã®éåã§ãããšè§£èª¬ããŸãããåäœå (Unit type) ã§ããå
·äœçãªå€ããäœããããªãã©ã«åã®éåã«ãã£ãŠéåå (Collective type) ãã string
åã number
åãboolean
åãªã©ã®ããªããã£ãåãæ§æãããŸãã
ãããŠãããããåã¯ãå³ (fig 2) ã®ããã«å
šäœéåãšãªã unknown
åã®éšåéåã§ãããnever
åã¯ç©ºéåãšããŠã¿ãªãããšãã§ããŸããã
fig 1 (åã¯å€ã®éå) | fig 2 (å šäœéåãšéšåéå) |
---|---|
åãªãã©ã«åã®ç©éåãç°ãªãéåå (Collective type) ã®ç©éåãã€ã³ã¿ãŒã»ã¯ã·ã§ã³åã§äœãããšãããšå
±éèŠçŽ ãå
šãç¡ãã®ã§ never
åãšãªããŸããããŸããnever
åã¯ç©ºéåãšããããšã§ãnever
åãã®ãã®ãšä»ã®åãšã®åéåããŠããªã³åã§äœæãããš never
åã¯ç¡ãã£ããã®ããã«ãŠããªã³åã®æ§æèŠçŽ ãšããŠäœ¿çšããèŠçŽ ã®åãã®ãã®ãšãªããŸãã
type StrOrNever = string | never;
// type StrOrNever = string; ãšåã
string
åã number
åãªã©ã®ããªããã£ãåãåæããŠãŠããªã³åãäœããšä»¥äžã®å³ã®ããã«ããããã®æ§æèŠçŽ ã®å (éå) ãåæããåéåãäœããŸããå
å«ãããŠããéšåéå (subset) ã¯ãããå
å«ããŠããäžäœã®éå (superset) ã«å¯Ÿã㊠subtype-supertype ã®é¢ä¿ã«ãããŸãã
S
åã T
åã® subtype ã§ãããšã S
㯠T
ã«ãã£ãŠå
æ (subsumption) ãããŠããŸããéåãšããŠã¯ S
ã T
ã«å
å«ãããã®ã§ S
ãšå€ã®éå S
ã¯ç°ãªããã®ã§ã)ã
ãã®èšäºã®ããŒãã§ãã Narrowing ãšã¯ãåã®çµã蟌ã¿ãã®ããšã§ããããåãçµã蟌ãããšããã®ã¯ç¹å®ã®å€æ°ãäžèšã®ãããªãŠããªã³åããããæ±ããããå ·äœçãªããªããã£ãåãç¹å®ã®ãªããžã§ã¯ããªãã©ã«ã®åãžãšç¯å²ãçµã蟌ãã§ããããã»ã¹ãè¡çºã®ããšãæããŸãã
Narrowing ã®å¿ èŠæ§
å
·äœçã« Narrowing ãã©ã®ãããªãã®ããèŠãŠã¿ãŸããnumber | string
ãšããïŒã€ã®ããªããã£ãåããæ§æããããŠããªã³åãåŒæ°ãšããŠåãå
¥ããé¢æ°ãèããŸããé¢æ°å
ã§ã¯ãäŸãã°æ¬¡ã®ãã㪠if
æãªã©ã§æ¡ä»¶å€å®ããŠç¹å®ã®ãã©ã³ãå
ã§ãŠããªã³åãããå
·äœçãªç¹å®ã®ããªããã£ãåã§ãããšçµã蟌ãããšãã§ããŸãã
// number | string åã®ã¿ãåŒæ°ãšããŠåãå
¥ããé¢æ°
function narrowUnion(
param: number | string
): void {
if (typeof param === "string") {
// param ã¯ãã®ãã©ã³ã㧠string åã§ãããšçµã蟌ãŸãã
console.log(param.toUpperCase());
// ^^^^^: string å
}
else if (typeof param === "number") {
// param ã¯ãã®ãã©ã³ã㧠number åã§ãããšçµã蟌ãŸãã
console.log(Math.floor(param));
// ^^^^^: number å
}
}
if
ã else if
ã®åãã©ã³ãã§å€æ°ã®åããŠããªã³åã§ã¯ãªãå
·äœçãªããªããã£ãåãšããŠçµèŸŒãã§ããã®ã§ãã®åã§äœ¿ãããããã¿ã€ãã¡ãœãããéçã¡ãœãããå©çšã§ããããã«ãªããŸãã
ãã®ãããªåã®çµã蟌ã¿ãè¡ããªãã£ãå Žåã«ã©ããªããèŠãŠã¿ãŸããããçµã蟌ã¿ã®ããã® if æãç¡ãããŠããããã®ã¡ãœãããå©çšããããšãããšåãšã©ãŒãšãªããŸãã
function narrowUnion(
param: number | string
): void {
console.log(param.toUpperCase())
// ^^^^^^^^^^^ [Error]
// Property 'toUpperCase' does not exist on type 'string | number'.
// Property 'toUpperCase' does not exist on type 'number'.
console.log(Math.floor(param));
// ^^^^^ [Error]
// Argument of type 'string | number' is not assignable to parameter of type 'number'.
// Type 'string' is not assignable to type 'number'
}
string | number
ãšãããŠããªã³å㯠string
åãš number
åã®åéåã§ããããã®é¢æ°ã¯ããããã®åã®å€æ°ãåãå
¥ããŸãããããŠããã®é¢æ°ã®åŒæ°ãšããŠæž¡ãå€æ°ãå
·äœçãªå€ã§ãããšãã«ã¯çµå±ã¯ string
åã®å€ã number
åã®å€ã®ã©ã¡ããã§ãã
æž¡ããå€æ°ã string
åã§ãã£ããšãã«ã¯ Math.floor()
ã¯äœ¿ããŸããããéã« number
åã§ãã£ããšãã«ã¯ toUpperCaser()
ã¡ãœããã¯äœ¿ããŸãããéçã¡ãœããã§ãã Math.floor()
ã« string
åãæž¡ããå Žåã«ã¯ NaN
ãåŸãããŸãããnumber
åã«å¯Ÿã㊠toUpperCase()
ã¡ãœãããåŒã³åºãããšãããšç¢ºå®ã«ãšã©ãŒãšãªããŸãã
ãã㯠JavaScript ãšããŠèšè¿°ããŠå®è¡ããã°åããããšã§ããJavaScript ã ãš TypeScript ã®æã«ãšãã£ã¿äžã§åŸãããäžèšã®ãããªåãšã©ãŒãã§ãŠããªãã®ã§å®è¡ãããŸã§ãšã©ãŒã«ãªããã©ããããããŸããã
function cantNarrowUnion(param) {
console.log(param.toUpparCase());
console.log(Math.floor(param));
}
cantNarrowUnion(42.3);
cantNarrowUnion("str");
TypeScript ã§ã®åã®å©äŸ¿æ§ãç¥ã£ãŠããç¶æ ã ãšããã¯éåžžã«æãããã§ãããæåååãæ°å€åãåŒæ°ãšããŠåãå ¥ããå Žåã«ã¯åãçµèŸŒãã§ãããã®åã§å©çšã§ããã¡ãœããã䜿ãããã«ããªããšãšã©ãŒã«ãªããŸãã
åã®çµã蟌ãå¿
èŠãããã®ã¯ãäžèšã ãš string | number
ãšããããããã§éãæäœäœç³»ãæã€éåã®åéåãšããŠåãã€ãã£ãŠããŸã£ãŠããããã§ããstring
åãš number
åã§ã¯æ±ãããããã¿ã€ãã¡ãœããããã®åã®å€æ°ã«å¯ŸããŠå ããããšã®ã§ããæäœãªã©ãå€ãã£ãŠããããã«å Žååããããå¿
èŠãã§ãŠããŸãã
string | number
ãšããåéåãã€ãã£ãæã«å
±éããŠå©çšã§ãããããã¿ã€ãã¡ãœãã㯠toString()
ã toLocaleString()
ãvalueOf()
ãªã©ã®éå®ããããã®ãããããŸããããããã®ã¡ãœãã㯠String.prototype.toString()
ãš Number.prototype.toString()
ã®ããã«ããããããåãååã®ãããã¿ã€ãã¡ãœããããšããŠå®çŸ©ãããŠããã®ã§å
±éããŠå©çšã§ããŸãããã®ãããªåãååã®ã¡ãœããã§ã¯ãªããã®ã䜿ãããå Žå (倧åã®å Žå) ã«ã¯åã®çµã蟌ã¿ãè¡ã£ãŠå Žååãããå¿
èŠãããããã§ãã
åãããããäžèšã®ãããªããªããã£ãåã®ãŠããªã³åã ãã§ã¯ãªããTypeScript ã§ã¯ undefined
ãšã®ãŠããªã³åãããåºçŸããŸãããªãã·ã§ã³åŒæ°ããªãã·ã§ãã«ããããã£ãªã©ã䜿ãããšã§åŒ·å¶çã«å泚éããåãš undefined
åãšã®ãŠããªã³åãšããŠã¿ãªãããŸãã
// ãªãã·ã§ã³åŒæ°ã䜿ã£ãé¢æ°å®çŸ©
function acceptOptionalStr(
str?: string // string | undefined ãšãªã
) {
// ...
}
acceptOptionalStr("text"); // string åãæž¡ãã
acceptOptionalStr(); // åŒæ°ã¯çç¥å¯èœ
string | undefined
ãšãããŠããªã³å㯠string
åãããåºãéå (superset) ãè¡šçŸããŠãããstring
åã undefined
åã® supertype ãšãªããŸããåºããšãã£ãŠãå®éã«ã¯å
·äœç㪠undefined
ãªãã©ã«ããæ§æããã Unit type ã§ãã undefined
åã stirng
åã«å ããã ãã§ãã
ãã®å Žåã« string
åã§å©çšã§ãããããã¿ã€ãã¡ãœãã toUpperCase()
ãé¢æ°å
ã§å©çšããããšãããšåãšã©ãŒã«ãªããŸãããã¡ãããundefined
ã«ã¯ããããã£ãšã㊠toUpperCase()
ãšãããããªã¡ãœãããæããªãããã§ãã
function acceptOptionalStr(
str?: string // string | undefined ãšãªã
) {
console.log(str.toUpperCase());
// ^^^ [Error]
// Object is possibly 'undefined'.
}
acceptOptionalStr("text"); // string åãæž¡ãã
acceptOptionalStr(); // åŒæ°çç¥ããéã«ã¯èªåçã« undefiend ãšãªããšã©ãŒãšãªã
ãã®ã³ãŒãã¯å®è¡æã«ãšã©ãŒãšãªããŸãããããåé¿ããã«ã¯ããã€ãæ¹æ³ã¯ãããšæããŸãããäŸãã° ES2020 㧠ECMAScript ã®æ°æ©èœãšããŠå°å
¥ããã Optional chaining æŒç®å(?.
) ã䜿ãããšã§åé¿ã§ããŸããããã䜿ãããšã§ undefined.toUpperCase()
ã®ãããªååšããªãããããã£ã¢ã¯ã»ã¹ã«ããäŸå€çºçãåé¿ã㊠undefined
ãè¿ãããšãã§ããŸãã
function acceptOptionalStr(
str?: string // string | undefined ãšãªã
) {
console.log(str?.toUpperCase());
// ^^ optional chaining æŒç®å
}
acceptOptionalStr("text"); // string åãæž¡ãã
// => "TEXT"
acceptOptionalStr(); // åŒæ°çç¥å¯èœ
// => undefined
Optional chaining æŒç®åã䜿ããã«ãnumber | string
ãŠããªã³ã§å©çšããããã« typeof
æŒç®åã if
æã®æ¡ä»¶ãšããŠå©çšããŠåã®çµã蟌ã¿ãããªã©ããã¡ããå¯èœã§ãããããã¯ãªãã·ã§ã³åŒæ°ã§ã¯ãªãããã©ã«ãåŒæ°ãšããããšã§ã§ãããã undefined
ãå
¥ã蟌ãŸãªãããã«ãããªã©ã®æ¹æ³ãããããŸãã
function acceptOptionalStr1(
str?: string // string | undefined
) {
if (typeof str === "string") {
console.log(str.toUpperCase());
}
};
function acceptOptionalStr2(
str?: string // string | undefined
) {
if (typeof str === "undefined") {
console.log(undefined);
} else {
console.log(str.toUpperCase());
}
};
function acceptOptionalStr3(
str = "str" // ããã©ã«ãåŒæ°
) {
console.log(str.toUpperCase());
};
ãšã«ããããŠããªã³åãšãªãå Žåã«ã¯æ§æèŠçŽ ãšãªãè€æ°ã®åå士ã§å ±éããŠäœ¿ããã¡ãœããã¯ããªãéå®çã«ãªãããé¢æ°å éšã§ã¯åãåãåŒæ°ã®åãçµã蟌ãã§å Žååãããå¿ èŠãã§ãŠããŸãã
ãªãã·ã§ã³åŒæ°ã®ããã«æ瀺çã«ãŠããªã³åãšããªããŠããŠããªã³åãšãªãå Žåãããã®ã§åã®çµã蟌㿠(Narrowing) ãéèŠã§ããããšãç解ã§ãããšæããŸãã
åç¯å²ã®æ¡å€§çž®å°
ãŠããªã³å (Union type) ã¯è€æ°ã®åãåæããåã§ããéåè«çã«ã¯è€æ°ã®éåã®åéå (å䜵: Union) ãšãªããŸããéã«ã€ã³ã¿ãŒã»ã¯ã·ã§ã³å (Intersection type) ã¯éåè«çã«ã¯ç©éå (å ±ééšåã亀差: Intersection) ãšãªããŸãã
äŸãã° string | number
ãªã©ããŠããªã³åã§ããããã㯠string
åãŸã㯠number
åãšããïŒã€ã®åãåãå
¥ããåæãããåã§ãããã®ããã«ïŒã€ã®åãçµã¿åãããããšããåã®åæ (Composing Types)ããšåŒã³ãŸããã
// let 宣èšããŠåå®çŸ©
let strornum1: string | number;
strornum1 = Math.random() < 0.5 ? "æåå" : 42; // äžé
æŒç®å
// type ã§åäœæ
type StrOrNum = string | number;
let strornum2: StrOrNum;
strornum2 = 42; // number åã®å€ã代å
¥ã§ããã
strornum2 = "æåå"; // string åã®å€ã代å
¥ã§ãã
ãã㧠Widening ã®åŸ©ç¿ãããŠãããŸããã次ã®ããã«äžé
æŒç®åã䜿ã£ãäžã§å€æ°ã®åæåãè¡ã£ãå Žåã«ã¯ãconst
宣èšãªãå
·äœçãªãªãã©ã«åã®ãŠããªã³åãšããŠåæšè«ãããlet
宣èšãªãäžè¬ç㪠string
ã number
åã®ãŠããªã³åãšããŠæ¡å€§ (Widening) ãããŠåæšè«ãããŸãã
const unionValConst = Math.random() < 0.5 ? "text" : 42;
// ^^^^^^^^^: "text" | 42 ãšããå
·äœçãªãªãã©ã«åã®ãŠããªã³åãšããŠåæšè«ããã
let unionValLet = Math.random() < 0.5 ? "text" : 42;
// ^^^^^^^^^^^ string | number ãšãããŠããªã³åã« Widening ãããŠåæšè«ããã
unionValLet = unionValConst;
// ãªãã©ã«å㯠Widening ããçµæã®åã® subtype ãªã®ã§ä»£å
¥å¯èœ
åã¢ãµãŒã·ã§ã³ã®ããã«ãas const
㧠const ã¢ãµãŒã·ã§ã³ããããšã«ãã£ãŠ Widening ãæå¶ã§ããŸããã
// _______________: "text" | 42 ãšãããªãã©ã«åã®ãŠããªã³åãšããŠåæšè«ããã
const unionValConstAs = Math.random() < 0.5
? "text" as const
: 42 as const;
// (const ã¢ãµãŒã·ã§ã³ã§ãããã Widening ãæå¶)
let unionValLetAs = unionValConstAs;
// ^^^^^^^^^^^^^ "text" | 42 ãšããå
·äœçãªãªãã©ã«åã®ãŠããªã³åãšããŠåæšè«ããã(Widening ã®æå¶ãç¶ç¶)
Literal Wideing ã§ã¯ãã®ããã«å
·äœçãªæååãªãã©ã«å (Unit type) ããäžè¬çãªããªããã£ãåã§ãã string
å (Collective type) ãžãšåãæ¡å€§ãããŸããã
éåè«çã«ã¯åæååãªãã©ã«åã¯ãã®ç¹å®ã®æååãªãã©ã«å€ã«ãã£ãŠæãåéå (singleton) ãããã¯åäœéå (unit set) ã§ããããã®æååãªãã©ã«åã®éåã string
åãæ§æããŠããŸãã
åŸã£ãŠ Widening ã§ã¯ã察象ãšãªãéåã subset ãã superset ãžãšç¯å²ãæ¡å€§ãããããšã«ãªããŸããsupertype-subtype ã®é¢ä¿æ§ã§èãããšãsubtype ã§ããæååãªãã©ã«åãã supertype ã§ãã string
åãžãšæ¡å€§ãããŸããéã« Narrowing ã§ã¯ã察象ãšãªãéåã superset ãã subset ãžãšçµã蟌ãããšã«ãªããŸããsupertype-subtype ã®é¢ä¿æ§ã§èãããšãsupertype ã§ãããŠããªã³åãã subtype ã§ãã string
åã number
åãžãšçµã蟌ã¿ãŸãã
éåè«çã«å³ç€ºãããšä»¥äžã®ãããªé¢ä¿ãšãªããŸãã
Widening ã§ã¯åºæ¬çã«ãªãã©ã«åããäžè¬çãªããªããã£ãåãžã®æ¡å€§ãèããNarrowing ã§ã¯ãŠããªã³åããç¹å®ã®ãããã¿ã€ãã¡ãœãããªã©ã䜿ããããã«ãªãããªããã£ãåãžã®çµã蟌ã¿ãèããŸãã
Narrowing ã§ã¯ãäŸãã° toUpperCase()
ãšããã¡ãœãã㯠string
åã§ãã䜿ããªãã®ã§ string | number
ãªã©ã®ãŠããªã³åããåã®åè£ãæžãã (reduce) ãŠãåã string
åãŸã§çµã蟌ã¿ãŸããnumber
åã§ãã䜿ããªãã¡ãœããã䜿ããããªã number
åãŸã§çµã蟌ã¿ãŸãã
å¶åŸ¡ãããŒè§£æ (CFA)
ãšããããšã§ããŠããªã³åãé¢æ°ã®åŒæ°ãšãªãããšã§ãé¢æ°å éšã§åŒæ°ã«å¯ŸããŠå©çšã§ããã¡ãœããããã®ãŠããªã³åã«å«ãŸããåã«ãã£ãŠå€ãã£ãŠããã®ã§å Žååããããå¿ èŠãã§ãŠããŸãã
// string åãŸã㯠number å ããã®ãŠããªã³åã§æ³šéãããå€æ°ãåãå
¥ãã(ãã以å€ã¯åãå
¥ããªã)
function strOrNum(
param: string | number
): void {
if (typeof param === "string") {
// string åã®ãããã¿ã€ãã¡ãœãã
console.log(param.toUpperCase());
} else { // string åã§ãªããªã number å
// number åã®å€ã«äœ¿ããéçã¡ãœãã
console.log(Math.floor(param));
}
}
ãããã£ãã³ãŒãã®æ§é ã«åºã¥ããŠå€ã®åãããå ·äœçã«æšå®ã§ããããã«ããããšã (åã®ç¯å²ãããå ·äœçãªãã®ã«çããããšãã) Narrowing(åã®çµã蟌ã¿) ãšåŒã³ãŸããã
ãããŠå®éã«ã¯ãäžã®ã³ãŒãã§ã® if
ç¯ã switch
ã while
ãªã©ã®ã³ãŒãã®æ§é ã«ãã£ãŠåå Žæã§ã®å€æ°ã®åãçµã蟌ã¿ãŸãããã®ãããªã³ãŒããæžããš TypeScript (ã³ã³ãã€ã©ããšãã£ã¿ã®æ¡åŒµæ©èœ) ã¯ããå€æ°ãç¹å®ã®ãã©ã³ããªã©ã«å°éããæç¹ã§ãã®åããªãã§ããã解æãããŠããŸãããã®è§£æããå¶åŸ¡ãããŒè§£æ(Control flow analysis: CFA)ããšåŒã³ãŸãã
ã¡ãªã¿ã« CFA ã§ãããTypeScript å ¬åŒã® ããŒãã·ãŒãã®ïŒã€ ãšããŠãŸãšããããŠããã®ã§ãã¡ãã確èªããŠãããšè¯ãã§ãã
CFA ã§ã¯ãŠããªã³åã®å€æ°ã®åãããã€ãã®çåœå€ã®ããžãã¯ãã¿ãŒã³ã«åºã¥ããŠåãçµã蟌ãã§ãããŸããåºæ¬çã«ã¯ãif
ç¯ã§æ¡ä»¶å€å®ããŸãããswitch
ãªã©ã䜿ãå ŽåããããŸãã
// ãã®ãã©ã³ãå
ã§å€æ°ã string åãšããŠçµã蟌ã
if (typeof input === "string") {
console.log(input);
// ^^^^^^ string åãšã㊠CFG ã§è§£æããã
console.log(input.toUpperCase());
// ãã®ãã©ã³ãã§ã¯ string åããŒã¿ã®ãããã¿ã€ãã¡ãœãããªã©ãå©çšã§ãã
} else {
// ãã®ãã©ã³ã以éã¯åã®åè£ãã string åãå€ããã
}
å®éã«ãŠããªã³åããããªããã£ãåã reduce ããŠã¿ãŸããïŒã€ã®ããªããã£ãåãšïŒã€ã®ãªãã©ã«å (Unit type) undefined
ã®èšïŒã€ã®åããæ§æããããŠããªã³åããïŒã€ãã€åããžããã«ã¯ä»¥äžã®ãããªã³ãŒããæžããŸã (æžããé çªã¯ããããŒã§ã)ã
// å³æå®è¡é¢æ°å
ã§ãããã 1/4 ã®ç¢ºçã§ç¹å®ã®åã®å€ãååŸããã
const st: undefined | string | number | boolean = (() => {
const r = Math.random() * 100;
if (r < 25) {
return 42;
} else if (r < 50) {
return "B";
} else if (r < 75) {
return false;
} else {
return undefined;
}
}();
// ãŠããªã³åããããªããã£ãåãïŒã€ã〠reduce ã㊠Narrowing ãã
if (typeof st === "boolean") {
st; // boolean
} else {
st; // string | number | undefined
if (typeof st === "undefined") {
st; // undefined
} else {
st; // string | number
if (typeof st === "string") {
st; // string
} else if (typeof st === "number") {
st; // number
} else {
st; // never (空éå)
}
}
}
CFA ã«ãã解æã«ãã£ãŠãšãã£ã¿äžã§å€æ°ã«ãããŒãããšå®éã«åãçµã蟌ãŸããŠããããšãåãããŸãã
ãããéåè«çã«å³ç€ºãããšä»¥äžã®ããã«ãªããŸããåã®åè£ããæ§æèŠçŽ ãšãªãå
·äœçãªåãæžãããŠãã£ãŠãundefined
åãæžãããšåã®åè£ã«ã¯ãªã«ãå
·äœçãªåãæ®ã£ãŠããããæçµçã«ã¯ç©ºéå never
åã«ãªããŸãã
Narrowing ãšã¯ãã®ããã«åºåãªåã®éåããæ¡ä»¶å€å®ã«ãã£ãŠããç¯å²ã®çãåã®éåãžãšå
·äœçã«åè£ãçµã蟌ãã§ããããšã«ä»ãªããŸãããå³ã®ããã« string
åãšãã£ãããªããã£ãåãŸã§çµã蟌ãã°ãã®åã®ãããã¿ã€ãã¡ãœãããªã©ãå©çšã§ããããã«ãªããŸãã
å€å¥å¯èœãªãŠããªã³å
å€å¥å¯èœãªãŠããªã³å (Discriminated union type) ãããã¯ã¿ã°ä»ããŠããªã³å (Tagged union type) ã¯åã·ã¹ãã äžè¬ã§ã¯ Sum å ãšåŒã°ããé¡ã®ãã®ã§ããã
Type system - Wikipedia ããåŒçš
å ·äœçã«ã¯ãç¹æ®ãªãŠããªã³åãã§ããããªããžã§ã¯ãã®åãåæããéã«ãã®ãå€å¥å¯èœãªãŠããªã³åããšããŠå®çŸ©ããŠããããšã§ Narrowing ããããããªããã®ã§ãã
éåè«ã§èãããšãå€å¥å¯èœãªãŠããªã³å㯠Disjoint union(é亀å) ãšåŒã°ãããã®ã«ãªããŸããé亀å (Disjoint union) ã¯ïŒã€ã®éåã®åéåãã€ãã£ãæã«å ±ééšåããªããã€ãŸã亀差 (intersection) ãæããªãåéåã®ããšãæããŸãã"disjoint" ãšã¯ãäºãã«çŽ ãã§ããããšãæå³ããŸãã
åã¯å
·äœçãªå€ã®éåã§ãç¹ã«ããªããã£ãåã¯å
·äœçãªãªãã©ã«åã®éåãšããŠã¿ãªããŸãããstring
åãš number
åã¯å
±éã®å
·äœçãªå€ãååšããªããããåéåãã€ãã£ãéã«ã¯å
±ééšåãç¡ãèªåçã« Disjoint union ãšãªããŸããç©éå (亀差) ãäœãåºãããšã€ã³ã¿ãŒã»ã¯ã·ã§ã³å㧠string
åãš number
åãåæããããšãããšç©ºéåã§å€ãæããªãããšãè¡šçŸãã never
åãšãªããŸãã
ãšããããšã§ãå®ã¯ä»ãŸã§ã®å·ŠãããªãŠããªã³åã®å³ã¯æ£ãããªããïŒã€ã®ããªããã£ãåããæããŠããªã³åã¯äº€å·®ãä»ã®èŠçŽ ãæããªããã string & number
ã never
ãšãªãããšãããããæ£ç¢ºã«å³ç€ºãããšå³ã®ããã«ãªããŸãã
äžèšã® string | number
ã®ãããªããªããã£ãåã®ãŠããªã³åã Narrowing ããéã«ã¯ãã§ã«ç¥ã£ãŠãã typeof
æŒç®åã§å€å¥ããã°ããã®ã§ç¹ã«åé¡ã¯ãããŸããã
type StrOrNum = string | number;
// Disjoint union ãäœæãã
function padLeft(pad: StrOrNum) {
if (typeof pad === "string") {
// string åãšã㊠CFA ã§çµã蟌ãŸãã
} else {
// number åãšã㊠CFA ã§çµã蟌ãŸãã
}
}
å®ã¯ãå€å¥å¯èœãªãŠããªã³åãšããŠç¥ãããŠããã®ã¯ãªããžã§ã¯ãã®åã«ã€ããŠã®ãŠããªã³åãèãããšãã®ãã®ã§ããèŠããã«ãªããžã§ã¯ãã®åã§ã®ãŠããªã³åã®äœãæ¹ã®ãã©ã¯ãã£ã¹ã®è©±ãšãªããŸãã
ç°ãªãããªããã£ãåå士ããŠããªã³åãšããŠåæãããšèªåçã« Disjoint union ã«ãªããŸããã (æ£ç¢ºã«ã¯ Disjoint union ã§ã¯ããã Discriminated union ã§ã¯ãªã)ããªããžã§ã¯ãåããŠããªã³åãšããŠåæãããš Disjoint union ã«ãªããšã¯éããŸãããäŸãã°ã{ a: "st" }
ãš { b: 42 }
ãšãããªããžã§ã¯ãã®åãåæãããšä»¥äžã®å³ã®ããã«äž¡æ¹ã®ããããã£ãæã€åã亀差ãšããŠåºçŸããŸãããªããžã§ã¯ããªãã©ã«ã«ããåã®è¡šçŸã¯å®éã«ã¯ãã®ããããã£ãšå€ã®åãæ¡ä»¶ãšããŠæºãããããããªããžã§ã¯ãã®éåãè¡šçŸããŸã (ä»ã®ããããã£ãæã£ãŠãããšããŠããã®åã®ç¯çãšãªãã®ã¯ TypeScript ãæ§é çéšååã®ã·ã¹ãã ãæ¡çšããŠããããã§ã)ã
ãšããããšã§ããªããžã§ã¯ãåã Disjoint union ãšããŠåæããŠã¿ã°ä»ããŠããªã³åãšããã«ã¯ããæ¹æ³ãåãå¿ èŠãã§ãŠããŸãããããŠããªããžã§ã¯ãåã®åæã§äº€å·® (intersection) ãåºçŸããªããªãããã㯠Disjoint union ã§ãããDiscriminated union ã§ããéã« Disjoint union ã§ã¯ãªããªããã㯠Discriminated union ã§ã¯ãªãããšã«ãªããŸãã
äŸãã°ããããäŸãšããŠå³åœ¢æ å ±ãè¡šçŸãããªããžã§ã¯ãã®åãèããŸããå ·äœçã«ã¯åè§åœ¢ (square)ãäžè§åœ¢ (triangle)ãå (circle) ã®ïŒã€ã®çš®é¡ã®å³åœ¢ã«ã€ããŠèããŸããå³åœ¢ãªããžã§ã¯ããåãåã£ãŠããããã£ãšããŠæãããå±æ§æ å ±ããäœãããã®èšç®ãããŠå€ãè¿ããããªé¢æ°ãäœããããšããŸãã
ãã®å ŽåãåŒæ°ã®å Shape
ãã©ã®ããã«å®çŸ©ããããšããã®ãåé¡ã«ãªããŸãã
ãŸãã¯æªãäŸãšããŠã次ã®ããã«ïŒã€ã®ãªããžã§ã¯ãã®åã®äžã«ãªãã·ã§ã³ããããã£ãå
¥ããŠããããæ¹æ³ãããããŸããkind
ããããã£ã«å³åœ¢çš®é¡ãæååãªãã©ã«åã®ãŠããªã³åã§æå®ã§ããããã«ããŠãããããã®å³åœ¢ã§ããããã£ãæå®ã§ããããã«ãªãã·ã§ã³ããããã£ãšããŠããŸããããã®æ¹æ³ã¯åã®ç²ŸåºŠããããªããç¡é§ã«åºãåãšãªã£ãŠããŸã£ãŠããŸãã"square" ã¿ã€ãã®å³åœ¢ãæ¬æ¥æããªããŠãè¯ãããããã£ãããããŠããŸãããšãã§ããŸãããé¢æ°ã®åŒæ°ã«ãšã£ãŠ Narrowing ããéã«äžéœåãã§ãŠããŸãã
ãŸãããã®æ¹æ³ã ãšå³åœ¢ã®çš®é¡ãè¿œå ããããšããªã©ã«ãå®ã¯äžäŸ¿ã§ãã
type Shape = {
kind: "square" | "triangle" | "circle";
radius?: number; // "circle" ã®å³åœ¢ãæã€ã¹ãããããã£
length?: number; // "square" ã®å³åœ¢ãæã€ã¹ãããããã£
angle?: number; // "triangle" ã®å³åœ¢ãæã€ã¹ãããããã£
};
const sORt: Shape = {
kind: "square",
lenth: 100,
radius: 50, // "square" ã§ã¯ç¡é§ãªããããã£
};
äžå¿åŸã§èª¬æããããããã«ãã® Shape
åã次ã®ããã«å解ããŠå®çŸ©ããªãããŠãããŸãããŠãŒãã£ãªãã£å Partical<Type>
ã䜿ãã°ååŒæ°ã«æå®ãããªããžã§ã¯ãåã®ããããã£ã optional ã«ã§ããŸãã®ã§ããããã€ã³ã¿ãŒã»ã¯ã·ã§ã³åã§åæããŠåãäœæããŸãããŸãããªãã·ã§ãã«ããããã£ã§ã¯ãªããã¹ãŠã®ããããã£ãå¿
é ãšãªããããªå Strict
ãå®çŸ©ããŠãããŸãã
type Kind = {
kind: "A" | "B" | "C",
};
type Props = {
r: number;
l: number;
a: number;
};
type Shape = Kind & Partial<Props>;
/*
type Shape =
{ kind: "A" | "B" | "C"; } &
{ r?: number; l?: number; a?: number; }
----------------------------------------
= {
kind: "A" | "B" | "C";
r?: number;
l?: number;
a?: number;
}
*/
type Strict = Kind & Props;
/*
type Strict =
{ kind: "A" | "B" | "C"; } &
{ r: number; l: number; a: number; }
--------------------------------------
= {
kind: "A" | "B" | "C";
r: number;
l: number;
a: number;
}
*/
ãã® Shape
åãš Strict
åã¯å¶çŽã®åŒ·ããšããŠã¯ Strict
åã®æ¹ã匷ããéåçã«èããŠã Shape
åã®æ¹ãåºãéåã§ãããStrict
åãå
å«ããŸãããããã£ãŠãShape
å㯠Strict
åã® supertype(superset) ã§ã (ããšã§ãŸãšããŠå³ç€ºããŸã)ãã¡ãªã¿ã«ããã¯æ¡ä»¶åã䜿ã£ãŠãå€å¥ã§ããŸãã
type FirstIsSubType<T, U> = T extends U ? true : false;
type StrictIsSubTypeOfShape = FirstIsSubType<Strict, Shape>;
// true
Shape
åã¯è¯ããªãäŸã§ããããã®ãããªåã¯æ¬¡ã®ããã«ãªããžã§ã¯ãã®åããããããã®å³åœ¢ã®åãšããŠåå²å®çŸ©ããäžã§ãŠããªã³åãšããŠåæããã»ãã䜿ãããããªããŸãããã®æ¹æ³ã§åæããå㯠Sum
åãšããååã«ããŠãããŸãã
type A = {
kind: "A"; // "circle"
r: number;
};
type B = {
kind: "B"; // "square"
l: number;
};
type C = {
kind: "C"; // "triangle"
a: number;
};
type Sum = A | B | C;
kind
ãšããå
±éã®ããããã£ãæã€ãªããžã§ã¯ãåã§ãŠããªã³åãåæããŠããç¹ãéèŠã§ããããã§ã¯ kind
ããããã£ã®å€ã®åãããããç°ãªãæååãªãã©ã«åãšããŠå®çŸ©ããŠããŸããããã«ãã£ãŠãŠããªã³åãšã㊠A
ãB
ãC
ã®ïŒã€ã®åãåæãããšãéåçã«ã¯åã¯äº€å·®ãæããªãäºãã«çŽ ã§ããåéå (Disjoint union) ãšãªããŸããå
±ééšåãç¡ãããšããããã®åã«ä»£å
¥å¯èœãªã®ã¯ãŠããªã³åã®æ§æèŠçŽ ãã®ãã®ã®ãããããšãªããŸãã
Shape
ãš Sum
ã®ïŒã€ã®åã¯æ確ã«éããŸãã®ã§æ³šæããŠãã ãããSum
åã¯ã¿ã°ä»ããŠããªã³å (å€å¥å¯èœãªãŠããªã³å) ã§ãã
type Shape = {
kind: "A" | "B" | "C";
r?: number; // => number | undefined
l?: number; // => number | undefined
a?: number; // => number | undefined
};
// ã¿ã°ä»ããŠããªã³å(å€å®å¯èœãªãŠããªã³å)
type Sum =
| { kind: "A"; r: number; }
| { kind: "B"; l: number; }
| { kind: "C"; a: number; };
ãã® Sum
ãšãããŠããªã³åã Disjoint union ã«ãªã£ãŠãããã©ããã¯æ§æèŠçŽ ãšãªãåã®äº€å·® (ã€ã³ã¿ãŒã»ã¯ã·ã§ã³å) ãåã£ãŠã¿ãã°ããããŸããA
ãB
ãC
ã®åã¯äºãã«çŽ ã§å
±éèŠçŽ ãšãªãå
·äœçãªå€ãååšããªãã®ã§ããããã€ã³ã¿ãŒã»ã¯ã·ã§ã³åã§äº€å·®ãåããš never
åãšãªããŸãã
type NeverAB = A & B;
// ^^^^^^^: never å
type NeverAC = A & C;
// ^^^^^^^: never å
type NeverBC = B & C;
// ^^^^^^^: never å
type NeverABC = A & B & C;
// ^^^^^^^^: never å
å
±éã®ããããã£ã®å€ã®åãç°ãªã£ãŠããã®ã§äº€å·®ã空éåãšãªãããã«ãªã£ãŠããŸã (ããã§ãªããªããžã§ã¯ãã®åå士ã§åæããã°äº€å·®ãã§ãŠããŸã)ããšããããšã§ Sum
åãã¿ã°ä»ããŠããªã³åã§ããããšã確èªã§ããŸããã
ãã®ã¿ã°ä»ããŠããªã³åã¯ãå€å¥å¯èœãªãŠããªã³åããšãåŒã°ããŸãããç¹å®ã®ããããã£ã®å€ (ãªãã©ã«å) ããå ã®å (ãããã¯éå) ãç¹å®ããããšãã§ããã®ã§ããå€å¥å¯èœããšããããã§ãã
å®éãããã䜿ãããšã«ãã£ãŠãŠããªã³åããæ§æèŠçŽ ã®åãžãš Narrowing ããŠçµã蟌ãããšãã§ããŸãã
function handleShape(shape: Sum) {
if (shape.kind === "A") {
// A åãšããŠçµã蟌ãŸãã
console.log(shape.r);
// ^^^^^^^: ããããã®åã«ååšããããããã£ã«ã¢ã¯ã»ã¹ãã§ãã
} else if (shape.kind === "B") {
// B åãšããŠçµã蟌ãŸãã
console.log(shape.l);
// ^^^^^^^: ããããã®åã«ååšããããããã£ã«ã¢ã¯ã»ã¹ãã§ãã
} else if (shape.kind === "C") {
// C åãšããŠçµã蟌ãŸãã
console.log(shape.a);
// ^^^^^^^: ããããã®åã«ååšããããããã£ã«ã¢ã¯ã»ã¹ãã§ãã
}
}
ãããå
ã® Shape
åã§ã Narrowing ã§ããŸããããã®åå®çŸ©ãã kind
ã®å€ã "A"
ã ã£ããšããŠã r
ããããã£ã¯ãªãã·ã§ãã«ã§ããå¿
ãååšããŠãããšã¯éããŸããã®ã§ãããããã£ã¢ã¯ã»ã¹æã« undefined
ãšãªãå¯èœæ§ããããŸããåŸã£ãŠåã®å®å
šæ§ãšããŠã¯ Sum
åãããäœããªããŸãã
ããããã®åãéåè«çã«å³ç€ºãããšä»¥äžã®ããã«ãªããŸãã
éåã®å å«é¢ä¿ã¯å³ãèŠãã°ããããŸãããéšåéå (subset) ãšãªãåããããå å«ããŠããäžäœã®éå (superset) ã«å¯Ÿã㊠subtype ãšãªããŸãã
Kind
åãæãæ¡ä»¶ãç·©ãã®ã§éåãšããŠã®ç¯å²ã倧ãããæŽã«æ¡ä»¶å¶çŽãä»ããŠãããšãã詳现ãªåãšãªã subtype ãžãšæŽŸçããŠãããŸããShape
ãStrict
ãSum
ã®ïŒã€åãæ¯èŒããŠã¿ããšãæåã«å®çŸ©ãã kind
以å€ã®ããããã£ããªãã·ã§ãã«ãª Shape
åã¯éåãšããŠã¯ããªã倧ããããšãããããŸããã€ãŸãå¶çŽãç·©ãããã§ããéã«ãã¹ãŠã®ããããã£ãå¿
é ã«ãã Strict
åã¯å¶çŽãéåžžã«åŒ·ãéåãå°ããããšãããããŸãããã㊠Sum
åã¯ãã®äžéã«äœçœ®ããŠãããæ¡ä»¶ãšããŠã¯ Shape
ããã匷ããStrict
ãããç·©ããªã£ãŠããŸãã
å³ãã Strict
ãå€å¥å¯èœãªãŠããªã³åã§ãããšèšããŸããå®é Strict
å㯠Sum
åã® subtype ã§ãããã©ã¡ãã®åã亀差 (å
±éèŠçŽ ) ããªãããšãããããŸããå³ãã Sum
åãæããŠè¡šç€ºãããš Strict
åã¯æ¬¡ã®ããã« Disjoint union ã§ãã亀差ãæã¡ãŸããã
Strict
åã¯å®ã¯æ¬¡ã®ããã«å€åœ¢ã§ããããããç°ãªãæååãªãã©ã«åã§ããå
±éã® kind
ããããã£ãæã€ãªããžã§ã¯ãåã®åæã§ããããšãããã¿ã°ä»ããŠããªã³åã§ããããšãæããã§ãã
type Strict =
| { kind: "A", r: number; l: number; a: number; }
| { kind: "B", r: number; l: number; a: number; }
| { kind: "C", r: number; l: number; a: number; };
ã§ãããr
ãl
ãa
ã®ïŒã€ã®ããããã£ããã¹ãŠå¿
é ãšãªã£ãŠããã®ã§ããããã Narrowing ããªããŠããã¹ãŠã®ããããã£ã¢ã¯ã»ã¹ãå¯èœã§ãããšããããšã¯ãå³åœ¢ã®çš®é¡ãšããŠãã€ã¹ãã§ã¯ãªãããããã£ããã¹ãŠå¿
ãæããªããŠã¯ãªããªãããšã«ãªãã®ã§åé·ã§ç¡é§ã§ãããã¢ãã«ãšããŠããã®åã¯äžé©åœã§ãã
ãŸã Sum
åã®ãããªå®çŸ©æ¹æ³ã䜿ãããšã§å¥ã®çš®é¡ã®å³åœ¢ã®åããŠããªã³åã®èŠçŽ ãšããŠè¿œå ããããšãã«ç°¡åã«è¿œå ã§ããŸãã
type D = {
kind: "D"; // äŸãã° "star" ãšããå³åœ¢ãšããŠæ³å®
s: number; // äžå¿åºãã端ç¹ãŸã§ã®è·é¢
};
type Sum =
| A // { kind: "A"; r: number; }
| B // { kind: "B"; l: number; }
| C // { kind: "C"; a: number; }
| D // { kind: "D": s: number; }
é¢æ°å 㧠Narrowing ããéã«ã察å¿ãããã©ã³ããå¢ããã°ããã ãã§ãã
Exausitveness check
ã¿ã°ä»ããŠããªã³åã§ãã®ããã«èŠçŽ ãå¢ãããæã«äžã®äŸã§ã¯ïŒã€ãããŠããªã³åã®æ§æèŠçŽ ããªãã®ã§èŠãç®ã§ãã¹ãŠãç¶²çŸ ããŠããããšãå€å¥ã§ããŸãããæ§æèŠçŽ ãäŸãã°ïŒïŒãè¶ ãããšèŠãç®ã§å€å¥ããã®ã¯é£ããã§ããé¢åã§ãããã
ãã㧠never
å (éåçã«ã¯ç©ºéå) ãå©çšããããšã§ã¿ã°ä»ããŠããªã³åã®æ§æèŠçŽ ãšãªãåãã¹ãŠãé¢æ°å
ã§å©çšããŠãããã©ãããèŠãç®ã«é Œãããã§ãã¯ããããšãã§ããŸãã
ãšããã®ããå
çšè¿°ã¹ããšãã Narrowing ã¯ãŠããªã³åãšããåéåããåæå
ã®å (éå) ãåãé€ããŠããè¡çºã§ããããŸãããã¹ãŠã®æ§æèŠçŽ ãšãªãåãåãé€ããšç©ºéåãã€ãŸãäœãèŠçŽ ãç¡ãéåãšãªãã®ã§æçµçã«ã¯ never
åãšãªããŸããæ§æèŠçŽ ããã¹ãŠåæããªããš never
åã«ããããšã¯ã§ããªãã®ã§ããã®æ§è³ªãå©çšããŠãããŠãšã©ãŒãèµ·ãããŠç¶²çŸ
ããŠãããã®ãã§ãã¯ãããã®ã網çŸ
æ§ãã§ãã¯(Exausitveness check) ã§ãã
å床ãã¿ã°ä»ããŠããªã³åã§ãã Sum
åãèããŠã¿ãŸããæŽã« E
åãå ããŠèŠãŸãããã
type E = {
kind: "E";
x: number;
};
// ïŒã€ã®åããæ§æãããã¿ã°ä»ããŠããªã³å
type Sum = A | B | C | D | E;
ãã® Sum
åãåŒæ°ã«åãé¢æ°ç¡ãã§ç¶²çŸ
æ§ãã§ãã¯ãããŠã¿ãŸããif
ã ãšæåæ°ãå€ããªãã®ã§ switch
ã§ãã£ãŠã¿ãŸãã
function handleShapeX(shape: Sum) {
switch (shape.kind) {
case "A": {
// A åãšããŠçµã蟌ãŸãã
console.log(shape.r);
return;
}
case "B": {
// B åãšããŠçµã蟌ãŸãã
console.log(shape.l);
return;
}
case "C": {
// C åãšããŠçµã蟌ãŸãã
console.log(shape.a);
return;
}
case "D": {
// D åãšããŠçµã蟌ãŸãã
console.log(shape.s);
return;
}
default: {
const _exhaustiveCheck: never = shape;
// Type 'E' is not assignable to type 'never'
return;
}
}
}
never
åã¯ãã¹ãŠã®åã® subtype ã§ãã Bottom type ã§ãããèªèº«ä»¥å€ã®åãã代å
¥ããããšã¯ã§ããŸãããåŸã£ãŠãã¿ã°ä»ããŠããªã³åã®å€æ°ã«å¯Ÿã㊠Narrowing ã®éçšã§åã®åè£ãæžãããŠãã£ãæã«ãã¹ãŠã®ååè£ã網çŸ
ããŠããªãå Žåã«ã¯åãšã©ãŒãšãªããŸããäžã®äŸã§ã¯ E
åãåè£ãããžãããŠããªãã®ã§ never
åã«ãªã£ãŠãããåãšã©ãŒãèµ·ããŠããŸããã€ãŸãããã¹ãŠã®åã®åè£ã網çŸ
ã§ããŠããªãããšãããããŸãã
ãã®ããã«åãšã©ãŒã«ãã£ãŠãŠããªã³åã®æ§æèŠçŽ ãç¶²çŸ ã§ããŠããªãããšã«æ°ã¥ãããšã§ç¶²çŸ ããŠãããã®ãã§ãã¯ãå¯èœãšãªããŸãã次ã®ããã«ä¿®æ£ããçµæãç¶²çŸ ããŠããã°åãšã©ãŒãšãªããŸããã
function handleShapeY(shape: Sum) {
switch (shape.kind) {
case "A": {
console.log(shape.r);
return;
}
case "B": {
console.log(shape.l);
return;
}
case "C": {
console.log(shape.a);
return;
}
case "D": {
console.log(shape.s);
return;
}
case "E": {
console.log(shape.x);
return;
}
default: {
// ãŠããªã³åã®æ§æèŠçŽ ããã¹ãŠç¶²çŸ
ããŠããã®ã§åãšã©ãŒãšã¯ãªããªã
const _exhaustiveCheck: never = shape;
// ^^^^^: never å
return;
}
}
}
Narrowing ã®ãã¿ãŒã³
ãã®è©±é¡ã¯å®çšç (å®è·µç) ãªè©±ã§ã¯ãããŸãããNarrowing ã«ã€ããŠã¯æ¬è³ªçãªè©±ã§ã¯ãªããšæããã®ã§ãå¥ã®èšäºã«ããŠãŸãšããããšã«ããŸãã (æªå®æãªéšåãå€ãã£ãã®ã§)ã
Discussion