search
LoginSignup
197

posted at

updated at

Organization

6歳娘「パパ、型による条件分岐はできないの?」

とある休日

娘「ねぇ、パパ!」
娘「switchやろ〜!」

ワイ「おお、ええで!娘ちゃん!」
ワイ「Switchやろう!」
ワイ「ほな、テレビをつけて・・・」

娘「テレビ?」
娘「何を言っているの、パパ?」
娘「TypeScriptのswitch文のことだよ?」

ワイ「ファッ!?」

switch文で何をしたいのか

娘「今ね、ショッピングサイトを構築してるところなの」

ワイ「ほうほう」

娘「それでね、手広く儲けようと思って」

ワイ「おお、ええやんか」

娘「個人ユーザーだけじゃなく、法人ユーザーも登録できるようにしようと思うの」

ワイ「なるほどな」

娘「言語はTypeScriptを使っているんだけど」
娘「ちょっと聞きたいことがあるの」

ワイ「おう、なんでも聞いてや」

あいさつ関数を作っている

娘「ショッピングサイトにログインしたときに・・・」

個人の場合 → 「無職 やめ太郎さん、こんにちは!」
法人の場合 → 「株式会社無職さん、こんにちは!」

娘「↑こんなメッセージを表示させたいから」
娘「そのためのあいさつ関数を作っているの」

ワイ「なるほどな」

娘「それでね」

型によって条件分岐させたい

娘「例えば、Elmっていう関数型言語なんかだと・・・」

Elm
-- greetingMessage関数は、ユーザーを受け取って、あいさつの文字列を返す

greetingMessage : User -> String
greetingMessage user =
    -- case式で処理を分岐
    case user of
        -- 個人の場合
        PersonalUser { familyName, givenName } ->
            familyName ++ " " ++ givenName ++ "さん、こんにちは!"

        -- 法人の場合
        CorporateUser { name } ->
            name ++ "さん、こんにちは!"

娘「↑こんな感じで」
娘「ユーザーがPersonalUserなのかCorporateUserなのかによって」
娘「処理を分岐できるじゃない?」

ワイ「そういえば、そうやったね」
ワイ「代数的データ型を使った、パターンマッチいうやつやね」

娘「そんな風に、TypeScriptで型による分岐1はできないの?」
娘「とりあえず、こんなカスタム型を定義してみたんだけど」

TypeScript
    // ユーザーには「個人ユーザー」と「法人ユーザー」がいまっせ!
    type User = PersonalUser | CorporateUser

    // 個人ユーザーは「姓」と「名」を持ってまっせ!
    type PersonalUser = {
      familyName: string
      givenName : string
    }

    // 法人ユーザーは名前だけを持ってまっせ!
    type CorporateUser = {
      name: string
    }

ワイ「なるほどな」
ワイ「しかし残念ながら」
ワイ「Elmみたいな代数的データ型の機能は」
ワイ「TypeScriptには無いし・・・」

  • PersonalUser型だった場合の処理
  • CorporateUser型だった場合の処理

ワイ「↑みたいな、型による条件分岐もできひんのや・・・」

TypeScriptの型は、実行時には存在しない

ワイ「なぜなら、TypeScriptで書いた型情報は、コンパイル時にコード上から消されてしまうんや」

娘「あ、そっか」
娘「コンパイルされると、普通のJavaScriptのコードになるんだもんね」

ワイ「せや」
ワイ「だから、TypeScriptで定義したカスタム型の情報を」
ワイ「if文やswitch文の条件に使うことはできひんのや」

娘「そうだよね・・・」

ワイ「できることと言えば・・・」

TypeScript
  if ("familyName" in user) {
    // 個人の場合
    return `${user.familyName} ${user.givenName}さん、こんにちは!`
  } else {
    // 法人の場合
    return `${user.name}さん、こんにちは!`
  }

ワイ「↑こんな感じで」

familyNameというプロパティを持っていたら個人ユーザー」

ワイ「とか、そんな感じで分岐させるしかないな」

娘「そっか・・・」
娘「せっかく型駆動で開発してるんだから」
娘「型によって分岐したかったな・・・」

しかし娘ちゃん、思い出した

娘「あっ!そういえば・・・!」
娘「パパ、あの絵本を見せて!」
娘「あの、ブルーベリーの絵本!」

ワイ「おお、ブルーベリー本のことか」

スクリーンショット

ワイ「絵本ちゃうけどな

ブルーベリー本に書いてあった

娘「パパ、やっぱりできるよ!」
娘「TypeScriptでも、代数的データ型みたいなことができるよ」
娘「擬似的にだけど」

ワイ「おお、そうなんか」

娘「うん」
娘「さっきの型に・・・」

TypeScript
    // ユーザーには「個人ユーザー」と「法人ユーザー」がいまっせ!
    type User = PersonalUser | CorporateUser

    // 個人ユーザーは「姓」と「名」を持ってまっせ!
    type PersonalUser = {
+     type: "PersonalUser"
      familyName: string
      givenName : string
    }

    // 法人ユーザーは名前だけを持ってまっせ!
    type CorporateUser = {
+     type: "CorporateUser"
      name: string
    }

娘「↑こう、プロパティとして型名を持たせてあげるの」
娘「値にタグをつけてあげる感じね」
娘「そうすることで・・・」

TypeScript
// greetingMessage関数は、ユーザーを受け取って、文字列を返す
const greetingMessage = (user: User): string => {
  switch (user.type) {
    // 個人の場合
    case "PersonalUser":
      return `${user.familyName} ${user.givenName}さん、こんにちは!`

    // 法人の場合
    case "CorporateUser":
      return `${user.name}さん、こんにちは!`
  }
}

娘「↑こう、型名による分岐ができるの」

ワイ「おお、良さげやな」
ワイ「Elmのパターンマッチにそっくりやな」
ワイ「しかも・・・」

個人の場合
スクリーンショット 2022-05-21 15.46.25.png
userPersonalUser型になっている


法人の場合
スクリーンショット 2022-05-21 15.46.43.png
userCorporateUser型になっている


ワイ「ちゃんと型の絞り込みもされとるやないかい」

娘「うん、だから例えば」
娘「法人なのに、間違ってfamilyNameにアクセスしようとすると・・・」

スクリーンショット 2022-05-21 16.24.28.png

娘「↑ちゃんとコンパイルエラーが表示されて」
娘「ミスが起こらないようになってるよ」

ワイ「おお、ほんまやな」
ワイ「でも、型による分岐と言うと語弊があるかもな」
ワイ「値の構造によって分岐しとるわけやからな」

娘「そうね」

まとめ

  • TypeScriptでも、擬似的に代数的データ型を再現できる
    • オブジェクト型を定義するときに、プロパティとして型名を持たせておく
    • パターンマッチ的なこともできる

ワイ「↑ってことやな」

娘「うん」

ワイ「こう、型名で分岐できると、なんかエエ感じやな」

娘「そうだね」
娘「思考に近い感じでコードが書けて、いい感じだね!」

〜おしまい〜

参考文献

いい本なので、よかったら買って読んでみてくださいやで。

新しい記事もよろしくやで

Zennにも記事を書いてみましたやで

noteも書いてますやで

  1. ElmのコードにあるPersonalUserCorporateUserは、ではなくバリアントと呼ばれるもので、型によって分岐している訳ではありません。値の構造によって分岐しています。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
197