Elmで入力状態を保持するときにMaybeを使うメリット



以下の内容は「僕はこうしているよ」っていう提案なので常に `Maybe` を使ったほうがいいよっていう主張ではありません。
むしろ「こういうときには `Maybe` 使わないようがいいよ」みたいな情報をいただければ、よりよい内容に進化させられるのでぜひご協力くださいm(_ _)m

## 概要

Elm は input タグの `value` を取得する方法を持たない[^1]ため、`Model` に input タグに入力された値を保持して、入力があるたびに `input` イベントや `change` イベントで `Model` の値を更新して管理します。

```elm
type alias Model =
    { inputEmail : String
    }


init : ( Model, Cmd Msg )
init =
    ( { inputEmail = ""
      }
    , Cmd.none
    )


type Msg
    = InputEmail String


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        InputEmail email ->
            ( { model
                | inputEmail = email
              }
            , Cmd.none
            )


view : Model -> Html Msg
view model =
    div
        []
        [ text "email"
        , input
            [ type_ "email"
            , onInput InputEmail
            , value model.inputEmail
            ]
            []
        ]
```

この記事では、input の値を保持する際に `Maybe` を使うことの利点を示します。
つまり、上記の例を以下のように書き換えるとどんな嬉しいことがあるかをご説明します。

```elm
type alias Model =
    { inputEmail : Maybe String
    }


init : ( Model, Cmd Msg )
init =
    ( { inputEmail = Nothing
      }
    , Cmd.none
    )


type Msg
    = InputEmail String


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        InputEmail email ->
            ( { model
                | inputEmail = Just email
              }
            , Cmd.none
            )


view : Model -> Html Msg
view model =
    div
        []
        [ text "email"
        , input
            [ type_ "email"
            , onInput InputEmail
            , value <| Maybe.withDefault "" model.inputEmail
            ]
            []
        ]
```

## 利点1: 未入力であることを明示できる

`Maybe` を使うことで、その入力欄が

* まったく入力されていない状態なのか (`Nothing`)
* 一度入力して、あとで空白に戻した状態なのか (`Just ""`)

を別の値で表現できます。

これができると、いろいろな応用がしやすくなります。

### 応用例: 初期状態では必須項目に「入力されていません」と表示しないUI

必須項目に入力がないときに、「入力されていません」と表示されるUIはよく見かけますが、
ページを開いた瞬間から表示されていると
「宿題やろうと思ってたのに、お母さんが『宿題やったの？』って聞いてくるからやる気なくなったじゃんか！」
っていう小中学生のころのあの気持ちを思い出してしまいます。

そこで、EFO (Entry Form Optimization) などでよく推奨されるのが、

* ページを開いた瞬間には必須入力のエラーを表示しない
* 送信ボタンを押したり、一度入力して空白に戻した時にはエラーを表示する

という挙動です。

* まだ何もいじっていない状態 (`Nothing`)
* 一度入力して空白に戻した状態 (`Just ""`)

の2つを `Model` の状態として明確に区別できていると、こういったUIが実現しやすくなります。

### 将来の拡張性

もちろん、必ずしも毎回そのようなUIになるとは限りませんし、
Elm は型とコンパイラに守られた言語なので、最初は `Maybe` を使わずに書いて、あとで必要になった時に `Maybe` を使うように書き換えてもデグレはほとんど起きないでしょう。

将来の拡張性のために今のうちに `Maybe` を使っておくか、YAGNI的にまずは `Maybe` なしで始めるか、状況によって使い分けるのが現実解になると思います。

## 利点2: `lazy` と併用しても正しく動く

[`input` や `textarea` はタグ自体が内部状態を持っているため lazy と合わせて使うと意図しない挙動を示します](http://qiita.com/arowM/items/95eca2266dbbfa528fef)。
詳しい説明はリンク先に譲りますが、全角で入力された内容を自動で半角に変更(僕はNormalizationと呼んでます)したりすると、入力欄に表示されている内容と `Model` に保持されている内容に齟齬が生じます。

リンク先の記事では独自に定義した専用の `IString` の仕様を提案していますが、実は単純に `Maybe String` に変更するだけでも `IString` と同じ効果があります。


## 記事への反応

「むしろ `Maybe` じゃ足りないこともある」という意見もいただきました。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">「未入力」と「入力済み空欄」をきちんと区別できるので最低限必須だと思ってる。最近Elm実装でAutocompletionを提供するにあたって、2状態じゃ足りなくなったので（「Autocompleによる入力済み」が欲しくなった）3状態Union typeを作って対応したこともあった <a href="https://t.co/1GhQNOiLgk">https://t.co/1GhQNOiLgk</a></p>&mdash; Gadagarr (@gada_twt) <a href="https://twitter.com/gada_twt/status/986264854397779968?ref_src=twsrc%5Etfw">2018年4月17日</a></blockquote>

`Model` はあくまでも現在の状態を保持するものなので、見た目上同じに **見える** からといって同じ状態にしてしまうのではなく、Union Type を活用して `Model` 上はできるだけ informative にすることで、結果的に変なフラグなども不要になってシンプルに拡張性高く記述することができると思います。

[![sakura-chan.jpg](https://qiita-image-store.s3.amazonaws.com/0/16345/cf8daaa3-9197-981a-8c2e-1b6394ad464e.jpeg)](https://twitter.com/hashtag/%E3%81%95%E3%81%8F%E3%82%89%E3%81%A1%E3%82%83%E3%82%93%E6%97%A5%E8%A8%98?src=hash)


## まとめ

`lazy` と併用し、Normalization を行う場合、「利点1」も加味して考えるのであれば、最初から `Maybe String` などを使うと便利です。
それ以外の場合は、状況に応じて `Maybe` を使う選択肢も視野に入れて設計するとあとあと困ることが少なくなると思います。


[^1]: 実際にはportを使ったりすればできますが、推奨するやり方ではないのでここでは無視します。