TypeScript における object の intersection (&)
Understanding the intersection in Typescript might be harder than you think を読んで改めて TypeScript の intersection
( &
のこと ) について「ふーん」となったのでメモ
※このブログ中の画像は全て上記の記事から転載している
型を集合 (より正確にいうと集合にまつわる数学) として捉えると考えやすいというのは、様々なところで言われているのでここでは細かく説明はしない
また集合における intersection
とは A ∩ B
(集合Aと集合Bの共通部分) のことである

TypeScript の型においては、A & B
とは A ∩ B
と同じことであり、すなわち 型Aが取りうる値の集合と型Bが取りうる値の集合の共通部分
と言える
詳しくは後述していくが 取りうる値の集合 というのが最大のポイントである
どういうことかというと、
type Hero = string;
type Villain = "Lex Luthor"
上記の例で Hero
という型の取りうる値の集合は無数の string
となる ( e.g. spiderman
, batman
, …. )。
一方で Villain
という型の取りうる値の集合は具体的な string
の値 "Lex Luthor"
1つだけである。
type Hero = string;
type Villain = "Lex Luthor"
type Intersection = Hero & Villain; // "Lex Luthor"
つまり Hero & Villain
という共通部分は "Lex Luthor"
である
では、以下のような場合はどうなるかというと、 Hero
と Villain
の取りうる値の集合に共通部分はないため空集合 ( never
) となる。
type Hero = "Clark Kent";
type Villain = "Lex Luthor"
type Intersection = Hero & Villain; // never
object
における intersection
type Hero = { hero: "Clark Kent" };
type Villain = { villain: "Lex Luthor" };
type Intersection = Hero & Villain; // { hero: "Clark Kent", villain: "Lex Luthor" };
さて本題にはなるが object
の intersection
になると話がちょっとだけややこしくなってくる
上記の Hero & Villain
の intersection
は型としては { hero: "Clark Kent", villain: "Lex Luthor" }
となる。
直感的には、 Hero
と Villain
には共通部分がないように見えるし、一見共通部分ではなく和を取ったような見え方になっている (そう見えない人もいるかもしれないが)。
これも 「取りうる値の集合の共通部分」 であることに目を向けると説明がつく
type Movie = { title: string };
// 以下は全て Movie という型に対して代入が可能
const movie = { title: "The Dark Knight" };
const movie = { title: "The Dark Knight", villain: "Joker" };
const movie = { title: "The Dark Knight", year: 2008, imdbUrl: "https://www.imdb.com/title/tt0468569/", hasGoodEnding: boolean; numberOfViewers: 13 };
上記のように object
の性質として雑にいってしまうと (key, value)
の型が一致しているものが少なくとも1つあれば型に対して整合性が取れていると考える。
したがって、 「取りうる値の集合」 としては、 { title: "The Dark Knight" }
さえ含まれれば無数に取ることが可能な集合であると考えることができる。
(上記の movie
が全て Movie
に対して適合であることからも言える)
type Hero = { hero: "Clark Kent" };
type Villain = { villain: "Lex Luthor" };
type Intersection = Hero & Villain; // { hero: "Clark Kent", villain: "Lex Luthor" };
そこで改めて上で挙げた例を再掲する
「取りうる値の集合の共通部分」 という見方をすると
以下の画像のように { hero: "Clark Kent", villain: "Lex Luthor" }
が共通部分であることがわかり納得がいくと思う。

(余談) string & { name: string }
type Intersection = string & { name: string }
さてこの intersection
はどうなるでしょう?
これまでの説明からすると string
と { name: string }
には共通で取れる値がないので空集合となる気がしてしまうが実際には never
にはならない。
実は、Why is it allowed to intersect primitives and objects in TypeScript? - Stack Overflow でも回答されているように常に 空集合
= never
となるわけではない。
どういうことかというと このコメント にもある通り、 never
にするにもそれなりの計算コストがかかるので絶対起き得ないようなケースまでは厳密には処理しないとしている。
(この辺りも非常に Pragmatic で TypeScript っぽいなと思う)
また今回の場合には実は Branded Type
というユースケースというかテクニックが存在するからという側面もある。
type SomeUrl = string;
type FirstName = string;
TypeScript では例えば上記のように書いたとしてもどちらも string
として処理されるためお互いに代入が可能となってしまう。
特に昨今においては Kotlin における Value Class
など SomeUrl
と FirstName
はお互いに代入不可となるように型を分けたいことが多々ある。
Branded Type
はまさにこのユースケースのために以下のように書くことで型が異なるためお互いに代入不可とするテクニックである
type SomeUrl = string & { __brand: 'SomeUrl' };
type FirstName = string & { __brand: 'FirstName' };
let x = 'http://www.typescriptlang.org/' as SomeUrl
let y = 'Bob' as FirstName
x = y // error となる
まとめ
- TypeScriptの
intersection
(&
)は、型を集合として捉えることで理解しやすくなる - object型では和集合のような見た目になるが、これは「取りうる値の集合の共通部分」という観点から説明できる
- また、
Branded Type
のような実用的なテクニックにもintersection
が活用されている