Note: カスタム型(Custom type)は Elm では以前“ユニオン型(union type)”と呼ばれていました。他のコミュニティでの名前としてはtagged unionADTなどがあります。

カスタム型(Custom type)

これまでBoolIntStringのような Elm に元から用意された型を使ってきました。ですが、自分の型をどうやって定義したらよいでしょうか?

例えば、チャットルームを作っているとします。全員に名前が必要ですが、永続的なアカウントを作らない人もいるかもしれません。そういう人にはチャットルームに入る度に名前を付けてもらうことにしましょう。

UserStatus型を定義し全ての可能なバリアントを列挙することで、この状況を表現することができます:

type UserStatus = Regular | Visitor

UserStatus型は 2 つの バリアント を持っています。ユーザーはRegularVisitorになれます。つまりユーザーをこのようなレコードで表すことができます:

type UserStatus
  = Regular
  | Visitor

type alias User =
  { status : UserStatus
  , name : String
  }

thomas = { status = Regular, name = "Thomas" }
kate95 = { status = Visitor, name = "kate95" }

こうすれば、誰がアカウントを持っているRegularか、通りすがっただけのVisitorなのかを把握できます。このやり方は難し過ぎるということはありませんが、もっとシンプルにできます。

上述のようにカスタム型と型エイリアスを1つずつ作成するのではなく、すべてを単一のカスタム型で表すことができます。RegularVisitorのバリアントにはそれぞれ関連するデータを持たせることができます。1今回の場合、関連するデータはStringの値です:

type User
  = Regular String
  | Visitor String

thomas = Regular "Thomas"
kate95 = Visitor "kate95"

名前のデータはバリアントに直接付与されたので、レコード型はもう必要ありません。

この方法の別の利点は、各バリアントごとに他のバリアントとは異なる専用のデータを持たせられることです。アカウントを持っているユーザーであるRegularユーザーがサインアップのときに年齢を登録することを考えてみてください。レコードではこのようなケースをうまく取り扱うことができませんが、カスタム型を自分で定義するなら何の問題もなく行えます。年齢を登録できるようにするためにRegularバリアントに関連するデータを追加しましょう。次の対話形式の例を見てください:

[ { "add-type": "User", "input": "type User\n = Regular String Int\n | Visitor String\n" }, { "input": "Regular", "value": "\u001b[36m<function>\u001b[0m", "type_": "String -> Int -> User" }, { "input": "Visitor", "value": "\u001b[36m<function>\u001b[0m", "type_": "String -> User" }, { "input": "Regular \"Thomas\" 44", "value": "\u001b[96mRegular\u001b[0m \u001b[93m\"Thomas\"\u001b[0m \u001b[95m44\u001b[0m", "type_": "User" }, { "input": "Visitor \"kate95\"", "value": "\u001b[96mVisitor\u001b[0m \u001b[93m\"kate95\"\u001b[0m", "type_": "User" } ]

名前と年齢を指定してRegular訪問者を定義してみてください ⬆️

上記の例では年齢を追加しただけですが、型のバリアントはかなり劇的に分岐することがあります。例えば、地域のチャットルームを提案できるようにするためにRegularユーザーに所在地を追加したくなるかもしれません。その場合はRegularバリアントに関連するデータを追加します!あるいは匿名ユーザーを用意したくなるかもしれません。Anonymousという3つめのバリアントを追加します。おそらく最終的には次のようになります:

type User
  = Regular String Int Location
  | Visitor String
  | Anonymous

何も問題はありません!ここで別の例を見てみましょう。

メッセージ

"The Elm Architecture"の節ではMsg型の定義の例をいくつか見てきました。この手の型は Elm では非常に一般的です。チャットルームアプリではこのようにMsg型を定義するかもしれません:

type Msg
  = PressedEnter
  | ChangedDraft String
  | ReceivedMessage { user : User, message : String }
  | ClickedExit

この型には4つのバリアントがあり、関連するデータを持つバリアントと持たないバリアントがあります。ReceivedMessageが実は関連するデータとしてレコードを持っていることに気づきましたか?これはまったく問題ありません。どんな型でも関連するデータとして持つことができます!そのため、どのユーザーからどんなメッセージを受けたかなど、ただの String や User の組み合わせでは何を意味するのか曖昧になってしまうような情報も非常に厳密に記述することができます。

型の設計

ある状況を非常に厳密に表現し始めるのにカスタム型は極めて強力です。例えばもし何かデータをロードされるのを待っているとしたら、その状況をこのようにカスタム型で表現したいかもしれません:

type Profile
  = Failure
  | Loading
  | Success { name : String, description : String }

Profile のデータの状態をLoadingから始めて、フェッチに失敗したらFailure、成功したらSuccessというように何が起こったかに応じて状態を遷移させることができます。このような設計はview関数を書くのを本当にシンプルにします。データの状態がカスタム型で表現されているのでview関数はデータをロードしているときも常に妥当な見た目を表示することができます。

ここまででカスタム型の作り方を知りました。次の節ではそれの使い方を学びましょう!

Note: カスタム型は Elm で最も重要な機能です。 特に一度でもより厳密にシナリオを設計しようとする習慣を持てば、カスタム型に非常に深みを感じるでしょう。わたしはこの深みを付録の集合としての型型のビット表現で共有しようと試みています。助けになれば幸いです!

1. 訳注: Elmを学び始めてつまづきやすいポイントがカスタム型です。途中でわからなくなってしまった方のために補足説明の記事を用意しました。どうぞお役立てください。またElm-jpのdiscordもぜひご活用ください。

results matching ""

    No results matching ""