Note: カスタム型(Custom type)は Elm では以前“ユニオン型(union type)”と呼ばれていました。他のコミュニティでの名前としてはtagged unionやADTなどがあります。
カスタム型(Custom type)
これまでBool
やInt
、String
のような Elm に元から用意された型を使ってきました。ですが、自分の型をどうやって定義したらよいでしょうか?
例えば、チャットルームを作っているとします。全員に名前が必要ですが、永続的なアカウントを作らない人もいるかもしれません。そういう人にはチャットルームに入る度に名前を付けてもらうことにしましょう。
UserStatus
型を定義し全ての可能なバリアントを列挙することで、この状況を表現することができます:
type UserStatus = Regular | Visitor
UserStatus
型は 2 つの バリアント を持っています。ユーザーはRegular
かVisitor
になれます。つまりユーザーをこのようなレコードで表すことができます:
type UserStatus
= Regular
| Visitor
type alias User =
{ status : UserStatus
, name : String
}
thomas = { status = Regular, name = "Thomas" }
kate95 = { status = Visitor, name = "kate95" }
こうすれば、誰がアカウントを持っているRegular
か、通りすがっただけのVisitor
なのかを把握できます。このやり方は難し過ぎるということはありませんが、もっとシンプルにできます。
上述のようにカスタム型と型エイリアスを1つずつ作成するのではなく、すべてを単一のカスタム型で表すことができます。Regular
とVisitor
のバリアントにはそれぞれ関連するデータを持たせることができます。1今回の場合、関連するデータはString
の値です:
type User
= Regular String
| Visitor String
thomas = Regular "Thomas"
kate95 = Visitor "kate95"
名前のデータはバリアントに直接付与されたので、レコード型はもう必要ありません。
この方法の別の利点は、各バリアントごとに他のバリアントとは異なる専用のデータを持たせられることです。アカウントを持っているユーザーであるRegular
ユーザーがサインアップのときに年齢を登録することを考えてみてください。レコードではこのようなケースをうまく取り扱うことができませんが、カスタム型を自分で定義するなら何の問題もなく行えます。年齢を登録できるようにするためにRegular
バリアントに関連するデータを追加しましょう。次の対話形式の例を見てください:
名前と年齢を指定して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もぜひご活用ください。 ↩