36
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Elmカスタム型超入門

Last updated at Posted at 2019-04-24

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

限定して並べる

カスタム型は他の型と同様に自分で定義出来る型になります。とても文法的に似たものに型の別名(タイプエイリアス)がありますが、それとは違い、全く本当に新しい型を定義出来ます。カスタム型ではない既存の型を思い浮かべてみましょう。例えば、Int型です。Int型の値を並べてみてよ!って言われた場合には無限に数字を並べることができます(安全な範囲はコンパイラ等で決まります)。String型も、"", " ", "a", "b", "ab",...挙げだしたらキリがないですね? 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を投げたいのがよくある言語の特徴だと思います。すべてのケースとはなんでしょうか?先程言った「並べてみてよ!」な値です。それではどうするでしょうか?自分で可能性を並べてみれば良いのです!

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のバージョンであったケースの抜け漏れのコンパイルエラーが望ましい形で叱ってくれるようになります。とても親切なエラーを出してくれます。

構造を分解して取り出す

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

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

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が付いてくるからです。 そのためカスタム型とレコード型の使い分けに困ってしまう方が多いです。それでは、先程説明した限定して並べる構造を分解して取り出すの能力をミックスするとどうなるでしょうか?

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

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

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

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

Elmでの使われ方

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

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ライフを!

36
17
1

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
  3. You can use dark theme
What you can do with signing up
36
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?