Elmを学び始めた方がよくつまづくポイントに「カスタム型」があります。
さくらちゃんも別の言語で最初にカスタム型のような概念に出会ったとき、頭がぐちゃぐちゃになりました。むじゅかしぃ😢
ということで、カスタム型につまづいた方の理解の助けになれるよう、解説していきます🖊
まずはEnumからはじめよう
別の言語でEnumとか列挙型とかいった概念を知っている方であれば、きっと以下の例までは理解しやすいと思います。
type Color
= Red
| Blue
| Green
ここでは色を表すための独自のあたらしい型Color
を定義しています。
Red
(赤)、Blue
(青)、Green
(緑)の3色を表現できます。
もちろん、文字列型を使って"Red"
"Blue"
"Green"
と表現することもできますが、それでは誤って "Glue"
みたいなデータを入れてしまう可能性があります。
厳密に「これらの値しかありえない」と分かっているなら、上記のようにとりうる具体的な値を決めた独自の型 カスタム型 を定義することでそういったミスを防げるわけです。
さて、この型定義は実はColor
という 型 を定義しているだけではありません。だって、型だけ定義してその型をもつ値をつくる方法がなかったら意味ないじゃないですか。
だから同時にRed
Blue
Green
という 値 も定義するのがこの構文の正体なんです。
言い方を変えれば、Red
Blue
Green
という Color
型の 定数 を定義していることになります。
定数は小文字からはじまるはずなのに、これだけは大文字はじまりの定数です。ひどいことをしますね!💢🐐
イメージとしては、上記の型定義によって以下のような 定数 が定義されているということです。
Red : Color
Red = (なんちゃらかんちゃら)
Blue : Color
Blue = (なんちゃらかんちゃら)
Green : Color
Green = (なんちゃらかんちゃら)
そして、Elmにおいて 定数 とは「引数なしの関数」と同じだということを少し頭の隅に置いておいてください🤔
型引数をマスターしよう
ここからがつまづきポイントです。
さて、よくある会員登録が必要なWebサービスを考えてみましょう。
利用者の利便性を考えるなら、はじめは会員登録せずにつかえるようにしたほうが喜ばれます。
そうして「いいな」と思ってもらった段階で会員登録させてより多くの機能を使ってもらうのです。
そんなアプリケーションで会員を表す型をつくってみましょう。
type UserType
= GuestUser
| RegularUser
先ほどと同じようにとりうる値を列挙しただけです。
さて、ユーザーにはユーザーIDなどの情報も存在します。
愚直にそれを実現するなら以下のような型エイリアスを用意するのではないでしょうか。
type alias User =
{ userType : UserType
, userId : UserId
}
type UserType
= GuestUser
| RegularUser
type alias UserId = String
型エイリアスについてはElm guideの型エイリアスの項目をご覧ください。
でもここで問題があります。ログインしていないユーザーはユーザーIDを持たないんです。
もちろん、苦肉の策としてログインしていない場合はuserId
フィールドの値を空文字列にするような運用も可能です。
でも以下のような「間違った」状態が存在しうることには変わりません。
okasinaUser : User
okasinaUser =
{ userType = GuestUser
, userId = "ゲストなのになぜかIDがあるよ"
}
ユーザーIDが存在しないはずのGuestUser
(ログインしていない場合)なのに、データ構造上はIDを持つことができてしまうのです。
Color
型の例でカスタム型と文字列型を比較して「カスタム型にすると、ありえない値を作れないようにできる」みたいなことを言っておきながらこの体たらくです。カスタム型をつかったのに、結局ありえない値をつくれちゃってるじゃん!💢🐐
ちょっと道端の草でも食べて落ち着いてください🌻 そういう需要を満たすために、実はカスタム型を使って以下のように定義することができます。
type User
= GuestUser
| RegularUser UserId
type alias UserId = String
RegularUser
(ログイン済みの場合)の方に、なんかUserId
型(実態はString
型)の値がくっついてますね?
こうやって書くと、User
型を定義するとともに、以下のような 関数 が自動的に定義されます。
GuestUser : User
GuestUser = (なんちゃらかんちゃら)
RegularUser : UserId -> User
RegularUser userId = (なんちゃらかんちゃら)
GuestUser
定数 (無引数の 関数 でもありました)は前述のColor
型で見たのと同じ形式です。
一方でRegularUser
の方は引数を1つ持つ 関数 になっています。
「上記のような型定義をすると、こういう関数が自動的に定義されるよ」という単なるお約束だと思ってください。
そしてここで自動的に定義されたRegularUser
という 関数 は、「引数で渡したUserId
型の値を 内包した User
型の値を返す」という関数です。
さて内包した とはどういうことでしょうか? 例えば以下のようにUser
型の 値 を定義してみます。
someUser : User
someUser = RegularUser "Sakura-chan"
このsomeUser
という 値 から、あとで"Sakura-chan"
という値を取り出せるというのが、「内包した」の意味するところです。
内包した値を取り出してみよう
実際に取り出すには、case
式を使います。User
型の値を受け取って、そのユーザーのIDを画面表示用文字列に変換する関数を作ってみましょう。
displayUserId : User-> String
displayUserId user =
case userId of
GuestUser ->
"ゲスト"
RegularUser userId ->
userId
こう定義すれば、以下のように使うことができます。
someUser1 : User
someUser1 = RegularUser "Sakura-chan"
someUser2 : GuestUser
someUser2 = GuestUser
displayUserId someUser1
--> "Sakura-chan"
displayUserId someUser2
--> "ゲスト"
これなら、「ログインしていないユーザーなのにIDを持っている」というありえない状態をつくれないように制限できますね🌷
もっと情報を内包させてみよう
さて、ログイン中のユーザーはユーザーIDの他にも氏名などを登録しているかもしれません。
そういう場合にも、以下のようにすれば対応できます。
type User
= GuestUser
| RegularUser UserId Name
type alias UserId = String
type alias Name = String
こうやって書くと、User
型を定義するとともに、以下のような 関数 が自動的に定義されます。
GuestUser : User
GuestUser = (なんちゃらかんちゃら)
RegularUser : UserId -> Name -> User
RegularUser userId name = (なんちゃらかんちゃら)
今度は、User
型の値を受け取って、そのユーザーの氏名を画面表示用文字列に変換する関数を作ってみます。
displayUserName : UserType -> String
displayUserName user =
case userId of
GuestUser ->
"ゲスト様"
RegularUser userId name ->
name
こう定義すれば、以下のように使うことができます。
someUser1 : UserType
someUser1 = RegularUser "Sakura-chan" "ヤギのさくらちゃん"
someUser2 : GuestUser
someUser2 = GuestUser
displayUserId someUser1
--> "ヤギのさくらちゃん"
displayUserId someUser2
--> "ゲスト様"
もっともっと情報を内包させるには
同じように引数を増やしていけば、電話番号やツノの本数などの情報も内包させることができます。
でも、実用的には以下のような定義のしかたがオススメです。
type User
= GuestUser
| RegularUser Profile
type alias Profile =
{ userId : String
, name : String
, phone : String
, horns : Int
}
そう、レコードを内包させちゃえばいいのです。
まとめ
カスタム型のポイントは、裏で内緒で 関数 が自動的に定義されていることに気づくことです。
もしこの説明でわからないところがあれば、Twitterでさくらちゃんにメンションしてください。
Qiitaのコメント欄に質問などを書いていただいても見ないのでご注意ください。
では、よいElmライフを🌹
さくらちゃんのツイッターをフォローする
さくらちゃんが書いた他の記事を見る
さくらちゃんが翻訳したElmの本を手に入れる
さくらちゃんの写真集を手に入れる