とある休日
娘「ねぇ、パパ!」
娘「switch
やろ〜!」
ワイ「おお、ええで!娘ちゃん!」
ワイ「Switchやろう!」
ワイ「ほな、テレビをつけて・・・」
娘「テレビ?」
娘「何を言っているの、パパ?」
娘「TypeScriptのswitch
文のことだよ?」
ワイ「ファッ!?」
switch
文で何をしたいのか
娘「今ね、ショッピングサイトを構築してるところなの」
ワイ「ほうほう」
娘「それでね、手広く儲けようと思って」
ワイ「おお、ええやんか」
娘「個人ユーザーだけじゃなく、法人ユーザーも登録できるようにしようと思うの」
ワイ「なるほどな」
娘「言語はTypeScriptを使っているんだけど」
娘「ちょっと聞きたいことがあるの」
ワイ「おう、なんでも聞いてや」
あいさつ関数を作っている
娘「ショッピングサイトにログインしたときに・・・」
個人の場合 → 「無職 やめ太郎さん、こんにちは!」
法人の場合 → 「株式会社無職さん、こんにちは!」
娘「↑こんなメッセージを表示させたいから」
娘「そのためのあいさつ関数を作っているの」
ワイ「なるほどな」
娘「それでね」
型によって条件分岐させたい
娘「例えば、Elmっていう関数型言語なんかだと・・・」
-- greetingMessage関数は、ユーザーを受け取って、あいさつの文字列を返す
greetingMessage : User -> String
greetingMessage user =
-- case式で処理を分岐
case user of
-- 個人の場合
PersonalUser { familyName, givenName } ->
familyName ++ " " ++ givenName ++ "さん、こんにちは!"
-- 法人の場合
CorporateUser { name } ->
name ++ "さん、こんにちは!"
娘「↑こんな感じで」
娘「ユーザーがPersonalUser
なのかCorporateUser
なのかによって」
娘「処理を分岐できるじゃない?」
ワイ「そういえば、そうやったね」
ワイ「代数的データ型を使った、パターンマッチいうやつやね」
娘「そんな風に、TypeScriptで型による分岐1はできないの?」
娘「とりあえず、こんなカスタム型を定義してみたんだけど」
// ユーザーには「個人ユーザー」と「法人ユーザー」がいまっせ!
type User = PersonalUser | CorporateUser
// 個人ユーザーは「姓」と「名」を持ってまっせ!
type PersonalUser = {
familyName: string
givenName : string
}
// 法人ユーザーは名前だけを持ってまっせ!
type CorporateUser = {
name: string
}
ワイ「なるほどな」
ワイ「しかし残念ながら」
ワイ「Elmみたいな代数的データ型の機能は」
ワイ「TypeScriptには無いし・・・」
-
PersonalUser
型だった場合の処理 -
CorporateUser
型だった場合の処理
ワイ「↑みたいな、型による条件分岐もできひんのや・・・」
TypeScriptの型は、実行時には存在しない
ワイ「なぜなら、TypeScriptで書いた型情報は、コンパイル時にコード上から消されてしまうんや」
娘「あ、そっか」
娘「コンパイルされると、普通のJavaScriptのコードになるんだもんね」
ワイ「せや」
ワイ「だから、TypeScriptで定義したカスタム型の情報を」
ワイ「if
文やswitch
文の条件に使うことはできひんのや」
娘「そうだよね・・・」
ワイ「できることと言えば・・・」
if ("familyName" in user) {
// 個人の場合
return `${user.familyName} ${user.givenName}さん、こんにちは!`
} else {
// 法人の場合
return `${user.name}さん、こんにちは!`
}
ワイ「↑こんな感じで」
familyName
というプロパティを持っていたら個人ユーザー」
ワイ「とか、そんな感じで分岐させるしかないな」
娘「そっか・・・」
娘「せっかく型駆動で開発してるんだから」
娘「型によって分岐したかったな・・・」
しかし娘ちゃん、思い出した
娘「あっ!そういえば・・・!」
娘「パパ、あの絵本を見せて!」
娘「あの、ブルーベリーの絵本!」
ワイ「おお、ブルーベリー本のことか」
ワイ「絵本ちゃうけどな」
ブルーベリー本に書いてあった
娘「パパ、やっぱりできるよ!」
娘「TypeScriptでも、代数的データ型みたいなことができるよ」
娘「擬似的にだけど」
ワイ「おお、そうなんか」
娘「うん」
娘「さっきの型に・・・」
// ユーザーには「個人ユーザー」と「法人ユーザー」がいまっせ!
type User = PersonalUser | CorporateUser
// 個人ユーザーは「姓」と「名」を持ってまっせ!
type PersonalUser = {
+ type: "PersonalUser"
familyName: string
givenName : string
}
// 法人ユーザーは名前だけを持ってまっせ!
type CorporateUser = {
+ type: "CorporateUser"
name: string
}
娘「↑こう、プロパティとして型名を持たせてあげるの」
娘「値にタグをつけてあげる感じね」
娘「そうすることで・・・」
// greetingMessage関数は、ユーザーを受け取って、文字列を返す
const greetingMessage = (user: User): string => {
switch (user.type) {
// 個人の場合
case "PersonalUser":
return `${user.familyName} ${user.givenName}さん、こんにちは!`
// 法人の場合
case "CorporateUser":
return `${user.name}さん、こんにちは!`
}
}
娘「↑こう、型名による分岐ができるの」
ワイ「おお、良さげやな」
ワイ「Elm
のパターンマッチにそっくりやな」
ワイ「しかも・・・」
個人の場合
↑user
はPersonalUser
型になっている
法人の場合
↑user
はCorporateUser
型になっている
ワイ「ちゃんと型の絞り込みもされとるやないかい」
娘「うん、だから例えば」
娘「法人なのに、間違ってfamilyName
にアクセスしようとすると・・・」
娘「↑ちゃんとコンパイルエラーが表示されて」
娘「ミスが起こらないようになってるよ」
ワイ「おお、ほんまやな」
ワイ「でも、型による分岐と言うと語弊があるかもな」
ワイ「値の構造によって分岐しとるわけやからな」
娘「そうね」
まとめ
- TypeScriptでも、擬似的に代数的データ型を再現できる
- オブジェクト型を定義するときに、プロパティとして型名を持たせておく
- パターンマッチ的なこともできる
ワイ「↑ってことやな」
娘「うん」
ワイ「こう、型名で分岐できると、なんかエエ感じやな」
娘「そうだね」
娘「思考に近い感じでコードが書けて、いい感じだね!」
〜おしまい〜
参考文献
いい本なので、よかったら買って読んでみてくださいやで。
新しい記事もよろしくやで
Zennにも記事を書いてみましたやで
noteも書いてますやで
-
Elmのコードにある
PersonalUser
やCorporateUser
は、型ではなくバリアントと呼ばれるもので、型によって分岐している訳ではありません。値の構造によって分岐しています。 ↩