[Elm] Maybeを活用する

## Maybeの基本的な使い方

「Maybe」という名前からもわかるように、Maybeを使うと「ないかもしれない」状態を表現することができる。
Maybeの定義は、`Just`（ある場合）と`Nothing`（ない場合）の2状態からなるCustomTypeである。

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

CustomTypeでcase文を用いて引数を取り出せるのと同様に、Maybeもcase文で値を取り出すことができ、ある場合とない場合で処理を分けることができる。

例）名前がある時はその名前を、名前がない時は"NO NAME"と出力する

```elm
getUserName : Maybe String -> String
getUserName userName =
    case userName of
        Just name ->
            name

        Nothing ->
            "NO NAME"

result =
    Debug.log "result"
        ( getUserName (Just "yuna")
        , getUserName Nothing
        )
    -- result: ("yuna","NO NAME")
```



## Maybe.withdefault

`Nothing`だった時の初期値を与えることができる。
先ほどの例の`getUserName`は`Maybe.withdefault`を使うと以下のように書ける。

```elm
getUserName : Maybe String -> String
getUserName userName =
    -- Justの場合: Justの引数の文字列が返る
    -- Nothingの場合: "NO NAME"という文字列が返る
    userName
        |> Maybe.withDefault "NO NAME"
```



## Maybe.map

`Just`だった時の値をmapして変換することができる。`Nothing`だった時は何もせず`Nothing`をそのまま返す。

```elm
hello : Maybe String -> Maybe String
hello userName =
    userName
        |> Maybe.map (\name -> name ++ "さんこんにちは！")

result =
    Debug.log "result"
        ( hello (Just "yuna")
        , hello Nothing
        )
    -- result: (Just "yunaさんこんにちは！",Nothing)
```



## Maybe.map2, map3,...

複数要素が全て`Just`の場合のみ値をmapして変換することができる。複数要素のうち1つでも`Nothing`であれば`Nothing`を返す。

```elm
introduce : Maybe String -> Maybe Int -> Maybe String
introduce name age =
    Maybe.map2 introduceStr name age

introduceStr : String -> Int -> String
introduceStr name age =
    "名前は" ++ name ++ "です。" ++ (String.fromInt age) ++ "歳です。"

result =
    Debug.log "result"
        ( introduce (Just "yuna") (Just 22)
        , introduce Nothing (Just 22)
        , introduce (Just "yuna") Nothing
        )
    -- result: (Just "名前はyunaです。22歳です。",Nothing,Nothing)
```



## Maybe.withdefaultとMaybe.mapを組み合わせる

冗長になりがちなcase文を使わずにMaybeを扱うことができる。

#### case文を用いた書き方

```elm
hello : Maybe String -> String
hello userName =
    case userName of
        Just name ->
            name ++ "さんこんにちは！"

        Nothing ->
            "無効なユーザーです"
```

#### Maybe.withdefaultとMaybe.mapを組み合わせた書き方

```elm
hello : Maybe String -> String
hello userName =
    userName
        |> Maybe.map (\name -> name ++ "さんこんにちは！")
        |> Maybe.withDefault "無効なユーザーです"
```

上記の例は説明のための例であまり実用性がないので、アプリケーション開発で実際に使えそうな例を2つ示しておく。

**例１）名前がある場合のみ画面に名前を表示する**

```elm
viewUserName : Maybe String -> Html msg
viewUserName userName =
    userName
        |> Maybe.map (\name -> div [] [text <| "user: " ++ name])
        |> Maybe.withDefault (text "")
```

**例２）名前がある場合のみ年齢を取得するためのコマンドを発行する**

```elm
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        GetAge ->
            let
                cmd =
                    model.userName
                        |> Maybe.map (\name -> getAge name)
                        |> Maybe.withDefault (Cmd.none)
            in
            ( model, cmd )

port getAge : String -> Cmd msg
```



## 変数の命名

Maybeの変数には変数名の頭に「maybe」とつけると、Maybe型であることがわかりやすいのでオススメ。

```elm
hello : Maybe String -> String
hello maybeUserName =
    maybeUserName  -- ここはMaybe型
        |> Maybe.map (\name -> name ++ "さんこんにちは！")  -- ここでのnameはJustの値
        |> Maybe.withDefault "無効なユーザーです"
```



## Maybeの多用に要注意

実際のアプリケーション開発では、例えばユーザがログインしている/いない、データを取得できている/いないなど、様々な場面でMaybeが活躍する。
Maybeは非常に便利だが、便利ゆえに大きいアプリケーションを作っていると、Maybeがどんどん増えてMaybe地獄になる。modelが持っているデータがほとんどMaybe型なんてことも...。
安易にMaybe型を使わず、よりわかりやすい表現がないか考えてみよう。

**パターン１）ユーザがログインしている/いない**

```elm
-- Maybeを使った場合
currentUser : Maybe User
    
-- CustomTypeを使ってログインしている/いないを表現
type CurrentUser
    = LoggedIn User
    | NotLogin

currentUser : CurrentUser
```

**パターン２）データを取得できている/いない**

```elm
-- Maybeを使った場合
users : Maybe (List User)
    
-- CustomTypeを使ってユーザ一覧情報の取得ができている/いないを表現
type Users
    = Loaded (List User)
    | NotLoaded

users : Users
```

上記の例を見て、「別に2パターンしかないならMaybeでよくね？いちいち型作るのメンドクセーじゃん！」と思うひともいるかもしれない。
しかし、Maybeは「ある/ない」しか表現できない。その「ある/ない」が、ログインできていないからないのか、ロード中だからないのか、といった「ある/ない」のコンテキストを失った型表現になっている。安易にMaybeを使わないメリットは、最初に実装した人がどういうコンテキストでその型を作ったかが明確になるというところにある。

例えば

```elm
users : Maybe (List User)
```

だと、usersがある時ってどういう状態のとき？ない時ってどういう状態のとき？ということが定義だけからは読み取ることができず、仕様やコード全体からそのコンテキストをくみ取らなければいけなくなる。これが

```elm
type Users
    = Loaded (List User)
    | NotLoaded
```
と書かれていると、「あ〜ロード中だからデータがないのか、なるほど」と他の実装者にもすぐに理解されるだろう。
もちろんコンテキストが自明な場合もあるので、私が言いたいのは「Maybeを使うな」ということではなく、「思考停止で安易にMaybeを使うのはやめよう！」ということです！