導入
ブルーベリー本を読み進める中で部分型の概念についてこんがらがったので、忘れないうちにまとめる。
部分型のおさらい
「型Sが型Tの部分型であるとは、S型の値がT型の値でもあることを指す」(ブルーベリー本より抜粋)
結論、オブジェクト型において、SがTの持つ型としての要素をすべて満たしていればよい。
type Human = {
name: string;
age: number;
}
type Animal = {
name: string;
}
このような2つの型があるとする。型Humanは型Animalの持つプロパティをすべて持っているが、型Animalは型Humanの持つプロパティをすべては持っていない。つまり、Human型を作ってしまえば、Animal型が必要な場合でも代用が効くということである。
このとき、型Humanは型Animalの部分型であるといえる。逆に、型Animalは型Humanの部分型であるとは言えない。
関数の部分型について
本題。TypeScriptにおいて関数は値であるため、型が定義されている。関数の部分型の関係は以下3つの観点から確認できる。
- 返り値
- 引数の型
- 引数の個数
1については、上述した部分型の概念から理解できるため省略。SがTの部分型であるとき、関数のから返ってきたS型の返り値をT型とみなしても問題ない、ということである。
2について
まず定義から確認する。
「型Sが型Tの部分型であるとき、『Tを引数に受け取る関数』の型は『Sを引数に受け取る関数』の型の部分型となる」(ブルーベリー本より抜粋)
ここで、SとTの包含関係が逆転しているようにみえるため混乱する。しかし、具体例をとって理解するとよい。
type Human = {
name: string;
age: number;
}
type Animal = {
name: string;
}
const callName = (obj:Animal) => {
console.log(obj.name);
}
const g: (obj:Human) => void = callName;
g({
name:'aaa',
age:100
})
callNameはAnimal型の引数をとる。このとき、この関数が取る引数内のプロパティはnameのみである。一方、gはHuman型の引数を取る。このときg内の処理を実行するためにはnameとageの2つがプロパティとして必要となる。
各々の引数が部分型であるとき、Animal型を引数に持つ関数は、Human型を引数に持つ関数よりも引数の条件が緩い。そのため、g = callnameとしたときにgの引数条件はcallNameの引数条件を満たしている。
ゆえに、callNameはgに対して代用が効く、すなわちcallNameはgの部分型であると言える。
3について
これは引数が少ない場合には多い場合に比べて代用が効くよね、という話である。
type Human = {
name: string;
age: number;
}
type Animal = {
name: string;
}
const callName = (obj:Animal) => {
console.log(obj.name);
}
const g: (obj1:Human, obj2:Animal) => void = callName;
g(
{
name:'aaa',
age:100
},
{
name:'bbb'
}
)
このような例のとき、callNameはgの部分型であるといえる。gは引数2つ取ってるけど、callNameはgの引数1つで十分なので、代用効くじゃんってだけ。
まとめ
自分としては関数の部分型2について理解するのに時間を食ったので、誰かの助けになれば幸いです。
ブルーベリー本はガチおすすめ.
参考文献
プロを目指す人のためのTypeScript(https://gihyo.jp/book/2022/978-4-297-12747-3)