↓新作もよろしくやで!
36歳ザコーダーの休日
ワイ「何やこのコード、全然動かへんやん」
ワイ「怖いな~怖いな~…なんか嫌だなあ~」
よめ太郎「(何で自分が書いたコード見て稲川淳二みたいに怯えとんねん・・・)」
よめ太郎「(そんな鳥肌立つようなクソコード書いてんのかいな・・・)」
娘(4歳)「ねぇ、パパ」
ワイ「なんや、娘ちゃん」
娘「ちょっと、作ってほしい関数があるの」
ワイ「またかいな」
ワイ「娘ちゃんはホンマに関数が大好きやなぁ」
ワイ「しゃあない、パパはプログラミング苦手やけど、頑張って作ったるわ」
娘「ありがとう、パパ」
今回の要件
ワイ「ほんで、今回はどんな関数を作ればええんや?」
娘「えっとね」
娘「'あ'
という文字列を渡したら」
娘「['あ', 'あ', 'あ']
という配列を返してくれる」
娘「そんな関数が欲しいの」
ワイ「そんなん何に使うんや・・・」
娘「その関数がなくて、友達が困ってるの」
ワイ「何やて!?」
ワイ「娘ちゃんの友達が!?」
ワイ「よっしゃ、もう細かい事情なんて聞いたりせん!」
ワイ「ワイが関数を書いたる!」
娘「ワ〜イ!」
ワイ「ワ〜イ!」
ただし
娘「ただし、間違って文字列以外のものを渡せないように」
娘「ちゃんとTypeScriptで書いてね」
娘「パパ、TypeScript書ける?」
ワイ「理論上は可能や」
ワイ「QiitaでたまにTypeScriptの記事を読んどるからな」
実装開始
ワイ「ええと、作るべき関数は・・・」
'あ'
という文字列を渡したら、
['あ', 'あ', 'あ']
という配列を返してくれる。
ワイ「↑これやったな」
ワイ「1つの文字列を受け取ったら」
ワイ「その文字列を3つ詰めた配列を返せばええんやから・・・」
function stringToArray(str) {
return [str, str, str];
}
ワイ「↑こんな感じやろ!」
よめ太郎「型が書いてないやん」
よめ太郎「娘ちゃん、さっき『型をつけて』って言うてたで」
ワイ「おお、せやったわ」
ワイ「ちゃんとTypeScriptで書かな」
よめ太郎「ちなみに、TypeScriptの環境構築はやっておいたで!」
ワイ「おお、流石よめ太郎や!」
TypeScriptで書き直す
ワイ「ええと」
ワイ「引数str
は文字列、つまりstring型やから」
ワイ「(str: string)
って書けばええな」
ワイ「戻り値は、文字列が入った配列やから・・・」
ワイ「string[]
やな」
ワイ「せやから・・・」
function stringToArray(str: string): string[] {
return [str, str, str];
}
ワイ「↑こうや!」
できた
ワイ「娘ちゃん、関数できたで!」
ワイ「これで友達も助か・・・」
娘「パパ、要件が増えちゃったの」
ワイ「(どどどういうこと・・・?)」
娘「さっきの関数に、数値も渡せるようにしてほしいの」
娘「友達が困ってるの」
ワイ「わ、分かったで・・・」
数値も渡せるようにしたい
ワイ「ええと」
ワイ「文字列型 または 数値型
っていうのは」
ワイ「確かstring | number
っていう風に表すんやったな」
ワイ「せやから」
function stringToArray(str: string | number): (string | number)[] {
return [str, str, str];
}
ワイ「こうや!」
ワイ「あれ、もうstringToArray
いう関数名はおかしいな」
ワイ「string型だけやなくてnumber型も受け取るもんな」
ワイ「関数名はstringOrNumberToArray
にしとこか」
ワイ「引数名もstr
じゃなくて」
ワイ「strOrNum
にしとこか」
function stringOrNumberToArray(strOrNum: string | number): (string | number)[] {
return [strOrNum, strOrNum, strOrNum];
}
ワイ「できたで!娘ちゃん!」
娘ちゃんによるレビュー
娘「うーん、ちょっと微妙かな」
ワイ「え、何で・・・」
娘「ううんと・・・」
文字列型か数値型の引数を受け取って
文字列か数値の配列を返す
娘「↑こういう型付けになっちゃってるから」
文字列型の引数を受け取って
数値の配列を返す
娘「↑こんな実装をしてても、型チェックを通っちゃうの」
娘「文字列を受け取ったのに、数値の配列を返すようなコードを間違って書けてしまう・・・」
娘「そんな隙ができちゃうの」
ワイ「ホンマや・・・」
ワイ「しかも・・・」
return [1, 'あ', 3];
ワイ「↑こんな感じで文字列や数値が混じってるような実装してても型エラーにならへんやん」
ワイ「これは微妙やな・・・」
ワイ「文字列を受け取ったときは文字列だけの配列を返したいんやもんな」
ワイ「引数と同じ型の配列を返すこと・・・」
ワイ「それを保証するにはどうしたらええんや・・・」
娘「よく考えてみてね」
娘「あとね、パパ」
娘「こんなときに言いにくいんだけど・・・」
またも要件追加
娘「引数として、オブジェクトと数値の配列も許可して欲しいの・・・」
ワイ「ぐぬぬ」
ワイ「(本格的に詰んできたわ・・・)」
とにかくやってみる
ワイ「文字列
または数値
またはオブジェクト
または数値の配列
・・・・」
ワイ「もうany型でええやろ」
よめ太郎「ごめん、あかんわ」
よめ太郎「環境構築したときにESLintの設定をしたんやけど」
よめ太郎「anyは禁止にしてもうたわ」
ワイ「ぐぬぬぬぬぬ」
よめ太郎「それに、anyを使うと」
よめ太郎「数値を受け取ったのにオブジェクトの配列を返す、とか」
よめ太郎「どんな実装してても何の型エラーにもならへん」
よめ太郎「静的型付言語のメリットなくなってまうわ」
よめ太郎「つまり、さっきよりも更にダメになってまうわ」
ワイ「せやな・・・」
ワイ「引数と同じ型の配列を返すことを型で保証せなあかんのやもんな・・・」
こんな機能があったらいいのにな
ワイ「もう、(string | number | object | number[])
とか書くのダルいし」
ワイ「引数と同じ型の配列を返すことも型で保証したいから」
ワイ「関数を呼び出すときに型を指定できたらええのになぁ」
今回はこの関数に、文字列を渡して使いまっせ!
ワイ「↑こんな感じにしたいんや」
ワイ「つまり、関数を呼び出すときに、引数として型も渡す」
ワイ「そんな機能があったらいいのになあ・・・」
娘(4歳)「あるよ」
ワイ「ファッ!?」
娘「ジェネリクスを使えばいいんだよ」
娘「普通、関数を呼び出すときは・・・」
const stringArray = stringToArray('あ');
娘「↑こんな感じで呼び出すけど」
娘「ジェネリクスを使って・・・」
const stringArray = stringToArray<string>('あ');
娘「↑こう書いてあげればいいんだよ」
ワイ「おお・・・!」
ワイ「<string>
の部分で型を渡すんやな・・・!」
娘「そう」
ワイ「でも、関数を定義している部分にも何か」
ワイ「その型引数を受け取るってことを書かないとあかんのやろ?」
娘「そうだよ」
娘「だから・・・」
function stringToArray<T>(str: T): T[] {
return [str, str, str];
}
娘「↑こんな感じだね」
ワイ「なるほどな」
ワイ「stringToArray<T>
の」
ワイ「<T>
の部分で型を受け取ってるんやな」
ワイ「ほんで、引数部分が(str: T)
ってことは・・・」
引数
str
はT型
でっせ!
ワイ「ってことかぁ」
ワイ「今回の例だとT
にはstring
が入ってくるから・・・」
引数
str
はstring型
でっせ!
ワイ「ってことになるんやな」
ワイ「ほんで、返り値の部分はT[]
って書いてあるから」
返り値は
T型
の値が入った配列でっせ!
ワイ「つまり」
返り値は
string型
の値が入った配列でっせ!
ワイ「ってことになるんやな」
ワイ「これなら、引数と同じ型の配列を返すことを型で保証できるな!」
娘「うん、あとね」
娘「戻り値の配列の要素数を間違えて」
娘「4個とか5個とかで実装してしまわないように」
娘「戻り値の型はT[]
より[T, T, T]
にした方がいいね」
ワイ「おお、そんな型の書き方もあるんやな」
ワイ「要素数が3個の配列ってとこまで型で保証できるんやね」
ワイ「ワイ、たまに間違って100個詰めちゃう癖があるから助かるわ」
ワイ「娘ちゃん、ありがとうやで!」
ジェネリクス完全理解ワイ
ワイ「ほな、ジェネリクスを使って」
ワイ「さっきの関数を完成させていくで!」
ワイ「ええと、この関数は」
ワイ「引数として、文字列
または数値
またはオブジェクト
または数値の配列
を受け取れるわけやから」
ワイ「関数名はstringToArray
やなくて、単にtoArray
にしておこか」
ワイ「引数もstr
じゃなくて単にarg
にしとくわ」
ワイ「型引数は、何らかの型が入って来るってことが分かりやすいように」
ワイ「SomeType
っていう名前にしておこか!」
function toArray<SomeType>(arg: SomeType): [SomeType, SomeType, SomeType] {
return [arg, arg, arg];
}
ワイ「↑こうやな!」
実際に関数を使うときは
文字列を渡す場合
const array = toArray<string>('あ');
数値を渡す場合
const array = toArray<number>(3);
オブジェクトを渡す場合
const array = toArray<object>({hoge: 1});
配列を渡す場合
const array = toArray<number[]>([0, 1, 2]);
ワイ「↑こんな感じで型を指定しながら関数を呼び出せばええんやな!」
よめ太郎「せやな」
よめ太郎「ちなみにそれくらいシンプルな例なら・・・」
const stringArray = toArray('あ');
const numberArray = toArray(3);
const objectArray = toArray({hoge: 1});
const arrayArray = toArray([0, 1, 2]);
よめ太郎「↑こんな感じで、型引数を渡さなくても問題なく呼び出せるで」
よめ太郎「TypeScriptのコンパイラちゃんが・・・」
コンパイラ「お、今回の引数は数値・・・つまりnumber型やな!」
よめ太郎「↑こんな感じで引数の型を推論してくれるからな」
よめ太郎「ほんで・・・」
コンパイラ「ほな、関数が返す値の型は
[number, number, number]
やな!」
コンパイラ「[number, number, number]
型以外の値を返そうとしてたら、エラーにせんとな!」
よめ太郎「ってな感じでチェックしてくれるねん」
ワイ「なるほどな〜」
とにかく完成した
ワイ「娘ちゃん!」
ワイ「遂にできたで!!!」
娘「パパ、余計なことしないで?」
ワイ「ファッ!!!???」
娘「つまり」
娘「型引数に具体的な名前をつけないで?」
ワイ「SomeType
のこと・・・?」
ワイ「何でなん・・・?」
抽象的すぎるやん
ワイ「T
とか抽象的すぎるやん・・・」
ワイ「もっと、何らかの型が入って来るってことが伝わるような」
ワイ「具体的な名前の方がええやん・・・」
娘「何らかの型っていう、すごく抽象的なものを表す名前なんだから」
娘「むしろ抽象的な名前がいいんだよ」
ワイ「でもさぁ、何らかの型なんやから」
ワイ「それを説明するための名前として」
ワイ「SomeType
とかさ・・・」
娘「いや、<>
の中には何らかの型が入って来るって決まってるんだから」
娘「その何らかの型ですよっていう説明、要る?」
娘「<T>
を見たら分かるじゃん」
娘「パパは、ペットの犬にマイドッグ
って名前をつけるの?」
娘「それに、SomeType
だって結局すごく抽象的じゃない?」
娘「ならもうT
でよくない?」
ワイ「ぐぬぬぬぬ・・・・」
ワイ「・・・ほな最初から自分で作れや!!!」
よめ太郎「(それはたしかに)」
〜おしまい〜