Elmカスタム型超入門

Elmを始めると躓く可能性がとても高い**カスタム型 (Custom type)**を噛み砕いて説明していこうと思います。なんとなく良さだったり、出くわしたときにウッてならずに実用に読んだり使えたりなるのが目的です。そのため~~代数的データ構造~~・~~直和~~・~~直積~~のようなキーワードは出さずに、多少厳密さを欠いて説明するのでご容赦ください。


# 限定して並べる

カスタム型は他の型と同様に自分で定義出来る型になります。とても文法的に似たものに[型の別名(タイプエイリアス)](https://guide.elm-lang.jp/types/type_aliases.html)がありますが、それとは違い、全く本当に新しい型を定義出来ます。カスタム型ではない既存の型を思い浮かべてみましょう。例えば、[Int](https://package.elm-lang.org/packages/elm/core/latest/Basics#Int)型です。Int型の値を並べてみてよ！って言われた場合には無限に数字を並べることができます(安全な範囲はコンパイラ等で決まります)。String型も、```"", " ", "a", "b", "ab",...```挙げだしたらキリがないですね？ Elmで作る仮に「段級位制」を導入したとします。段位に応じて支払われるお金を決める関数を定義すると以下のようになるでしょう。

```elm
prizeMoney : Int -> Int
prizeMoney grade =
    case grade of
        1 ->
            100

        2 ->
            200

        3 ->
            300

        4 ->
            400

        5 ->
            1000

        _ ->
            -1 -- 例外とかnull投げたい
```

Elmは厳密な言語なのでif式を書いたらelseも必須。case式もすべてのケースを満たさなければコンパイラが通してくれません。仕方なく-1を返していますが例外やnullを投げたいのがよくある言語の特徴だと思います。すべてのケースとはなんでしょうか？先程言った「並べてみてよ！」な値です。それではどうするでしょうか？自分で可能性を並べてみれば良いのです！

```elm
type Grade
    = Ichi
    | Ni
    | San
    | Yon
    | Go


prizeMoney : Grade -> Int
prizeMoney grade =
    case grade of
        Ichi ->
            100

        Ni ->
            200

        San ->
            300

        Yon ->
            400

        Go ->
            1000

-- > prizeMoney Yon
-- 400 : number
```

これで例外やnullを投げる必要が無くなりシンプルでイージーにプログラミングをすることができます。さらに、Intのバージョンであったケースの抜け漏れのコンパイルエラーが望ましい形で叱ってくれるようになります。[とても親切なエラー](https://qiita.com/ababup1192/items/8dae437da47f25811658)を出してくれます。

# 構造を分解して取り出す

自分で定義した型に値を持たせたい場合があります。よくあるのはユーザを表す型です。

```elm
type alias Id =
    Int


type alias Name =
    String


type User
    = User Id Name -- User Int String と等価


getName : User -> Name
getName user =
    case user of
        User _ name ->
            name

-- getName (User _ name) =  -- 「パターンが一つの場合」こう書くことも出来ます。
--    name

-- > john = User 1 "John"
-- > getName john
-- "John": String
```

caseはとても強力で中の構造を分解して取り出せます。ただ、このようなケースの場合、レコードを使ったほうが楽です。

```elm
type alias Id =
    Int


type alias Name =
    String


type alias User =
    { id : Id, name : Name }

-- > user = User 1 "John"
-- > user.name
-- "John": String
-- > { user | id = 2 }
-- { id = 2, name = "John" }
--     : { id : Id, name : Name }
```

何故なら漏れなく、コンストラクタ(呼び出し方はカスタム型と同じです。)・Getter・Setterが付いてくるからです。 そのためカスタム型とレコード型の使い分けに困ってしまう方が多いです。それでは、先程説明した**限定して並べる**と**構造を分解して取り出す**の能力をミックスするとどうなるでしょうか？

# 限定して並べて、分解する

さっそく例を見てみましょう。

```elm 
type alias Id =
    Int


type alias Name =
    String


type User
    = User Id Name
    | Guest


getName : User -> Name
getName user =
    case user of
        User _ name ->
            name

        Guest ->
            "Guest"

-- > john = User 1 "John"
-- > getName john 
-- "John": String
-- > getName Guest
-- "Guest": String
```

まとめると、可能性を限定して安全なプログラミングが行えて、データ構造を分解して扱える、と言う一度で２度美味しい仕組みを手に入れることができました。


# Elmでの使われ方

言ってしまえばカスタム型は言語が持つ組み込みの型(IntやStringなど)とレコード型以外はすべてカスタム型で表現されています。とてもわかりやすくカスタム型の特徴を活かしているのが[Maybe](https://package.elm-lang.org/packages/elm/core/latest/Maybe#Maybe)型です。この型は値があるかないかを2つのパターンとして「限定して並べて」いる型です。今までの説明に加えて、一つ違うポイントは型変数*a*を持っているポイントです。値があるかないかと言うのは汎用的な仕組みなためどんな型でも受けれるようにしているということですね。(以下の例はMaybe Intの場合) それではMaybe型から値を取り出すときの`withDefault`の実装をパターンマッチ(case式の並べて分解する手法)で想像してみましょう。

```elm
type Maybe a
    = Just a
    | Nothing

-- > Maybe.withDefault 100 (Just 42)
-- > 42
-- > Maybe.withDefault 100 Nothing
-- > 100

withDefault : a -> Maybe a -> a
withDefault defaultValue aMaybe =
    case aMaybe of
        Just a ->
            a

        Nothing ->
            defaultValue
```

これで色んな型が出てきても「限定して並べて」「構造を分解して取り出す」の呼吸で扱うことが出来るはずです！それでは素敵なElmライフを！