Elm 0.18 入門 (2)パラメータ付きのアクションと入力フォーム

[前回](http://qiita.com/mather314/items/e9e08960d91b187652e5)に引き続き、サンプルアプリケーションを構築しながらElmを学んでいきましょう。

今回はカウントアップ機能を拡張しながら、入力フォームの使い方やエラーハンドリングの処理を学んでいきましょう。

- 増加幅のパラメータ化
- フォーム入力によるパラメータの更新
- 文字列の数値変換とエラーハンドリング

## 増加幅をパラメータ化する

前回までの内容では `Increase` は `+1` するのみでした。
まずはこの `+1` をパラメータにしてみましょう。

```diff
 type Msg
     = NoOp
-    | Increase
+    | Increase Int
```

引数を設定したので、 `Increase 1` などのようにアクションに対応できるように `update` を変更します。

```diff
 update : Msg -> Model -> Model
 update msg model =
     case msg of
         NoOp ->
             model

-        Increase ->
-            { model | count = model.count + 1 }
+        Increase num ->
+            { model | count = model.count + num }
```

この `Increase` はViewの `onClick` で呼び出されているのでした。こちらも呼び出し方を変更します。

```diff
 increaseButton : Html Msg
 increaseButton =
     div []
-        [ button [ onClick Increase ]
+        [ button [ onClick (Increase 1) ]
             [ text "+1" ]
         ]
```

ここで気をつけたいのが、 `onClick Increase 1` と書くとエラーになる点です。
括弧がない場合は `1` が `onClick` の第2引数とみなされてしまうので、必ず `(Increase 1)` と括弧で囲います。

これでコンパイル可能です。ただ、増加幅をパラメータ化しただけで値は同じなので、前回までの動きと一切替わりません。
試しに `Increase 5` のように呼び出し方を変えたものを追加してみましょう。

```hs
increaseButton : Html Msg
increaseButton =
    div []
        [ button [ onClick (Increase 1) ] [ text "+1" ]
        , button [ onClick (Increase 5) ] [ text "+5" ]
        , button [ onClick (Increase -1) ] [ text "-1" ]
        ]
```

コンパイルしてみて動作確認できるでしょうか？

ココまでのコード -> https://ellie-app.com/46Wjz8WRPzda1/1

## 増加幅のフォーム入力

増加幅の数値自体をアプリケーションの入力項目にしてみましょう。
まず、入力フォームの文字列（入力時点では数値ではない）をアプリケーション内部の状態として受け取れるようにします。

```diff
 type alias Model =
     { count : Int
+    , countStepInput : String   
     }
```

初期値は空文字にしておきましょう。

```diff
 initModel : Model
 initModel =
     { count = 0
+    , countStepInput = ""
     }
```

続いて、この値を更新するためのアクションを追加します。

```diff
 type Msg
     = NoOp
     | Increase Int
+    | UpdateCountStepInput String
```

```diff
 update : Msg -> Model -> Model
 update msg model =
     case msg of
         NoOp ->
             model

         Increase num ->
             { model | count = model.count + num }

+        UpdateCountStepInput s ->
+            { model | countStepInput = s }
```

最後に、Viewからこのアクションを呼び出すフォームを作ります。

```hs
stepInput : Model -> Html Msg
stepInput model =
    form []
        [ input [ onInput UpdateCountStepInput, value model.countStepInput ] []
        ]
```

フォームを全体のViewに組み込みます。(ついでに内部状態が見えるように出力を追加してます）

```diff
 view : Model -> Html Msg
 view model =
     div []
         [ counter model
         , increaseButton
+        , stepInput model
+        , text ("countStepInput = " ++ model.countStepInput)
         ]
```

追加したタグやイベント、更にHTMLの属性値 (value) が増えました。 import を変更しておきましょう。

```diff
-import Html exposing (Html, text, p, div, button)
-import Html.Events exposing (onClick)
+import Html exposing (Html, text, p, div, button, form, input)
+import Html.Events exposing (onClick, onInput)
+import Html.Attributes exposing (value)
```

コンパイルして動作確認してみましょう。入力した内容がちゃんと出力されていますか？

### `onInput` について

`onClick (Increase 1)` のときと異なり、 `onInput UpdateCountStepInput` ではアクションに渡す引数を指定しませんでした。公式ドキュメントを確認してシグネチャの違いを確認しましょう。

https://package.elm-lang.org/packages/elm-lang/html/latest/Html-Events

```
onClick : msg -> Attribute msg
onInput : (String -> msg) -> Attribute msg
```

ここで、 `msg` は型引数（各アプリケーションで実際の方が決まる）で、今回のアプリケションでは `Msg` になります。
つまり、 `onClick` では `Msg` そのものを引数にするのに対し、 `onInput` では `String -> Msg` という関数を引数にしています。
`UpdateCountStepInput` は `String` を引数にして `Msg` を返す関数そのものであるため、 `onInput` の引数にすることができました。

ココまでのコード -> https://ellie-app.com/46Wjz8WRPzda1/2

## 入力の数値変換とエラーハンドリング

さて、入力を取得することができたので入力された値を増加幅にしましょう。
増加幅を定義します。

```diff
 type alias Model =
     { count : Int
     , countStepInput : String
+    , countStepNum : Int
     }


 initModel : Model
 initModel =
     { count = 0
     , countStepInput = ""
+    , countStepNum = 0
     }
```

アクションも追加しましょう。このルーチンにだんだん慣れてきましたか？

```diff
 type Msg
     = NoOp
     | Increase Int
     | UpdateCountStepInput String
+    | UpdateCountStepNum Int


 update : Msg -> Model -> Model
 update msg model =
     case msg of
         NoOp ->
             model
 
         Increase num ->
             { model | count = model.count + num }

         UpdateCountStepInput s ->
             { model | countStepInput = s }
+            
+        UpdateCountStepNum num ->
+            { model | countStepNum = num }
```

Viewを作成する前にちょっと考えておくべきことがあります。
`countStepInput` はどんな文字列でも入力してしまえるため、数値に変換しようとするときに数値以外のケースで問題が起こることは容易に想像できます。
その問題は文字列を数値に変換する関数 `toInt` のシグネチャに具体的に現れています。

https://package.elm-lang.org/packages/elm-lang/core/latest/String#toInt

```hs
toInt : String -> Result String Int
```

さらに `Result` 型の定義はこちらです。

https://package.elm-lang.org/packages/elm-lang/core/latest/Result

```hs
type Result error value
    = Ok value
    | Err error
```

`Ok` は成功したときの値を返し、 `Err` でエラーが発生していることを表現しています。つまり、 `toInt`
 の実行にエラーが起こりうることを `Result String Int` 型として表現してます。
今回はエラーが発生した場合はなにもしないことにしましょう。

```hs
convertInputToMsg : String -> Msg
convertInputToMsg s =
    case (toInt s) of
        Ok num ->
            UpdateCountStepNum num

        Err msg ->
            NoOp
```

ここで初めて `NoOp` を実際に使いました。「何もしない」というアクションが定義されていることで利点のある例です。

さて、フォームに入力された値を変換してモデルを更新できるようにしましょう。

```diff
 stepInput : Model -> Html Msg
 stepInput model =
     form []
         [ input [ onInput UpdateCountStepInput, value model.countStepInput ] []
+        , input [ type_ "button", onClick (convertInputToMsg model.countStepInput), value "Update"] []
         ]
```

`type_` というボタン用の属性値が増えました。これを import しておきましょう。
なお、 `type` は予約語であるためHTMLの属性を表す関数は `type_` となっています。

```diff
-import Html.Attributes exposing (value)
+import Html.Attributes exposing (value, type_)
+import String exposing (toInt)
```

ただ、このままでは実際に増加させるボタンがありません。ボタンを変更しておきましょう。

```diff
-increaseButton : Html Msg
-increaseButton =
+increaseButton : Model -> Html Msg
+increaseButton model =
     div []
         [ button [ onClick (Increase 1) ] [ text "+1" ]
-        , button [ onClick (Increase 5) ] [ text "+5" ]
-        , button [ onClick (Increase -1) ] [ text "-1" ]
+        , button [ onClick (Increase model.countStepNum) ] [ text ("Add " ++ (toString model.countStepNum)) ]
         ]
```

モデルの状態を入力にしたので、呼び出し元も変更しましょう。

```diff
 view : Model -> Html Msg
 view model =
     div []
         [ counter model
-        , increaseButton
+        , increaseButton model
         , stepInput model
         , text ("countStepInput = " ++ model.countStepInput)
         ]
```

さて、上手く行ったでしょうか？

![https://gyazo.com/2cf7ea86e260d88359bf3bad8e4bbf07](https://i.gyazo.com/2cf7ea86e260d88359bf3bad8e4bbf07.gif)

他に数値でない文字列や、ゼロやマイナスの値などを入力して動作確認してみましょう。
また、DEBUGを押して実際に発行されているアクションに関してもチェックしてみましょう。

ココまでのコード -> https://ellie-app.com/46Wjz8WRPzda1/3

## まとめ

パラメータ付きのアクションの定義と、数値変換のエラーハンドリングについて見てきました。
いかがだったでしょうか？

次回は `beginnerProgram` から飛び出してアクションの連鎖などをやってみましょう。
http://qiita.com/mather314/items/01acf5d72b36dc55bbe1