[Elm] 型安全は堅苦しくない？ シンプルかつ柔軟にHTMLのイベントを捌いてみよう！

Elmは静的型付けで、さらに制約の強い型システムを採用したプログラミング言語です。こう聞くと、同じWEBフロントで活躍するJavaScript等と比較すると、何やらとっつきづらそうで書くのが大変そうなイメージを持ちます。確かにElmは実行時に安全に動かすために、コンパイラが危険なコードを防ぐため、コードを書くのが煩わしい部分があります。今回では誰もが一度はそう思うであろう[HTMLのイベント](https://qiita.com/ababup1192/items/53e4dfd42789c8bf9a5e)に関する煩わしいコードの回避法と逆に一度、扱いやすい型が確定すると逆に堅苦しさが消えて良いということをお見せしたいと思います。今回は以下の二つのコードを通して説明を行っていきたいと思います。なおスライダーの例は、[とりさん](https://twitter.com/TypedTypelessTy/status/1280649502475481088?s=20)の例を使用させていただきました。ありがとうございます！



- [年齢選択](https://ellie-app.com/9nSmFDKfy9pa1)
- [スライダー](https://ellie-app.com/9nSxq5XKmNRa1)


## 書き手が絶対ありえないと思う煩わしい分岐

まず初めに以下のような、年齢を選択するセレクトボックスを考えてみましょう。

<img width="148" alt="スクリーンショット 2020-07-11 7.34.33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48687/aae4420c-2758-08e6-bfca-a35ee55f35a5.png">

selectとoptionのリストを組み合わせることでセレクトボックスを構築します。せっかくElmなので、セレクトボックスには必ず年齢を表す整数(Int)しか来ないように、`ageOptions`という内部関数を定義します。これで書き手は、**必ず**整数しか選択されないことを知っています。テストなんて書きようがありません。勝ちました。ご視聴ありがとうございました。

```elm
view : Model -> Html Msg
view model = 
    let
        ageOptions : List Int -> List (Html Msg)
        ageOptions  intList =
            intList  |> List.map
                    (\age ->
                        option [ value <| String.fromInt age ] [ text <| String.fromInt age ]
                    )
    in
    div []
        [ select [ onChange UpdateAge ]
            (ageOptions <| List.range 0 50)
        , span [] [ text <| "あんたは" ++ String.fromInt model.age ++ "歳" ]
        ]
```

・・・がしかし、現実は非常です。UpdateAgeが受け取るのはStringです。String.toIntでMaybeをパターンマッチし、Nothingには来ないことを知りつつも無駄な分岐を書くことになります。これはElmが間違っているわけではありません。Htmlの`target.value`には制約上Stringしか渡すことができません。Stringが来る以上、整数の変換は失敗を考慮しなければなりません。しかし、そうなると気合を入れて内部関数を定義した部分が無駄に感じてしまいます。これは煩わしいですね・・・。

```elm
type Msg
    = UpdateAge String


update : Msg -> Model -> Model
update msg model =
    case msg of
        UpdateAge ageText ->
            case String.toInt ageText of
                Just age ->
                    { model | age = age }

                Nothing ->
                    model
```

### イベントが渡してくる値を加工して型を確定させよう

[ElmでHTMLのイベントに詳しくなろう](https://qiita.com/ababup1192/items/53e4dfd42789c8bf9a5e)の記事にも書いた内容ですが、selectboxが持っている値(状態)はJavaScriptが`change`イベントの発火時にコールバック関数より`target.value`が運んでくる内容です。それをElmがただ乗りしているためStringが値として運ばれてきます。その値は、[Html.Events.targetValue](https://package.elm-lang.org/packages/elm/html/latest/Html-Events#targetValue)関数の返す値を[Json.Decoder](https://package.elm-lang.org/packages/elm/json/latest/Json-Decode)で任意の`msg`に加工することができ、[Html.Events.on](https://package.elm-lang.org/packages/elm/html/latest/Html-Events#on)関数で自由に発火することができるのです。Stringの値をIntに変換し分岐を書く部分は同じですが、正常な処理を行うmsgを`intMsg`として注入してあげ、起こり得ない処理は`noOp`(msg)として空振りさせて上げ、msgを大きく分岐させることで関心ごとの分離を行っています。しかも、この関数は一度定義してしまえば、Intとして扱いたいときにどこにでも使い回すことができます。さらにどのイベントでも使えるように一般化することができますが、それは次のスライダーで扱います。

```elm 
onIntChange : msg -> (Int -> msg) -> Html.Attribute msg
onIntChange noOp intMsg =
    Html.Events.targetValue
        |> Json.map
            (\inputText ->
                case String.toInt inputText of
                    Just inputInt ->
                        intMsg inputInt

                    Nothing ->
                        noOp
            )
        |> Html.Events.on "change"
```

それでは完成品のModelとViewになります。先ほどの違いとしては、onIntChange関数にNoOpとUpdateAgeを渡すというイベントに変わっただけです。

```elm
type alias Model =
    { age : Int }


initialModel : Model
initialModel =
    { age = 0 }

view : Model -> Html Msg
view model = 
    let
        ageOptions : List Int -> List (Html Msg)
        ageOptions  intList =
            intList  |> List.map
                    (\age ->
                        option [ value <| String.fromInt age ] [ text <| String.fromInt age ]
                    )
    in
    div []
        [ select [ onIntChange NoOp UpdateAge ]
            (ageOptions <| List.range 0 50)
        , span [] [ text <| "あんたは" ++ String.fromInt model.age ++ "歳" ]
        ]
```

それでは煩わしかった`update`関数を見てみましょう。そうです。UpdateAgeの中は何もすることがなくなりました。これでageが晴れてIntであることが確定したため、自由に計算することができ柔軟で安全な世界が訪れます。

```elm 
type Msg
    = UpdateAge Int
    | NoOp


update : Msg -> Model -> Model
update msg model =
    case msg of
        UpdateAge age ->
            { model | age = age }

        NoOp ->
            model
```

### おまけ(もっともっと安全に)

今回はNoOpで異常な値を空振りさせましたが、型安全で保証した上で、万が一の異常を検知したい場合は、[ポート](https://guide.elm-lang.jp/interop/ports.html)経由でSentryエラー通知をすることもできます。滅多に異常検知をする必要がないElmですが、不安な分岐が多い場合には有効活用すると良いでしょう。

## システムが間違った値を送ってくるわけないはずでは？ スライダー

それでは、スライダーについてみていきます。Htmlでは、`<input type="range">`([MDN](https://developer.mozilla.org/ja/docs/Web/HTML/Element/Input/range))を使うことでスライダーを用意できます。しかしイベント発火時に送られてくる`target.value`の値はやはり、Stringです。スライダーは不動小数点を扱え、さらに`input`(動かしている間)と`change`(動かし終わり)の二つのイベントを扱うため、加工処理を抽出した`onFloatEvent`関数で一般化しています。今回はスライダーが100%数値以外を送ってこないと確信した上で、NoOpではなく`Maybe.withDefault`で0.0に丸め込みます。

```elm 
onFloatEvent : String -> (Float -> msg) -> Html.Attribute msg
onFloatEvent eventName floatMsg =
    let
        inputText2FloatMsg : String -> msg
        inputText2FloatMsg inputText =
            floatMsg <| Maybe.withDefault 0.0 <| String.toFloat <| inputText
    in
    Html.Events.targetValue
        |> Json.map inputText2FloatMsg
        |> Html.Events.on eventName


onFloatChange : (Float -> msg) -> Html.Attribute msg
onFloatChange floatMsg =
    onFloatEvent "change" floatMsg


onFloatInput : (Float -> msg) -> Html.Attribute msg
onFloatInput floatMsg =
    onFloatEvent "input" floatMsg
```

ModelとViewの定義になります。

```elm
type alias Model =
    { value : Float }


initialModel : Model
initialModel =
    { value = 0.0 }


view : Model -> Html Msg
view model =
    Html.div []
        [ sliderFloatView -3.14 3.14 0.01 model.value SliderChanged
        , text <| String.fromFloat model.value
        ]


sliderFloatView min max step value change =
    Html.input
        [ Attr.type_ "range"
        , Attr.min <| String.fromFloat min
        , Attr.max <| String.fromFloat max
        , Attr.step <| String.fromFloat step
        , Attr.value <| String.fromFloat value
        , onFloatChange change
        , onFloatInput change
        ]
        []
```

update関数です。スライダーを扱っているのですから、updateは素直にFloatの値を受け取るだけにしたいですね。全然煩わしくないです！

```elm
type Msg
    = SliderChanged Float


update : Msg -> Model -> Model
update msg model =
    case msg of
        SliderChanged value ->
            { model | value = value }
```

## まとめ

Elmは他の言語からすると一見、慎重すぎる部分があるとっつきづらい言語に見えます。しかし、一度扱いたい型に固定されると豊富な関数群がデフォルトで用意されているため、とても柔軟で表現力豊かになります。今回はHtml Eventにフォーカスして異常な値の変換を一極集中させることで煩わしさの回避をしました。それでは楽しいElmライフを！