Elm 0.18 入門 (3)アクションの連鎖

[前回](http://qiita.com/mather314/items/4dc12c418e80b7a8373e)は簡単なフォームを作成して、入力をモデルの状態に反映させてみました。

https://ellie-app.com/4b78MtJKJZka1/0

今回はちょっとアプリケーションのフレームワークを高度なものに変更して、アクションを起点に別のアクションを発生させてみましょう。

## `beginnerProgram` から `program` へ

それではまずビギナーを卒業しましょう。
これまで使っていた `Html.beginnerProgram` のシグネチャはこちらでした。

```hs
beginnerProgram :
    { model  : model
    , view   : model -> Html msg
    , update : msg -> model -> model
    } -> Program Never model msg
```

一方、 `Html.program` はこうなります。（比較のために公式ドキュメントより項目の順序を少し変更しています）

```hs
program :
    { init          : (model, Cmd msg)
    , view          : model -> Html msg
    , update        : msg -> model -> (model, Cmd msg)
    , subscriptions : model -> Sub msg
    } -> Program Never model msg
```

- `init` (`model`だった部分) の型に `Cmd msg` が追加されたタプルになっている
- `update` の最後の返り値も同じく型が変わっている
- `subscriptions` が追加されている
- `Cmd`, `Sub` って何だろう？

などの違いや疑問点があると思います。実際にコードを変更していきながら説明していきましょう。
ただ、今回は `subscriptions` は何もしません。

### `initModel` : 初期化関数の変更

`initModel` の初期化時に `(Model, Cmd Msg)` を返すようにします。

```diff
-initModel : Model
+initModel : (Model, Cmd Msg)
 initModel =
     { count = 0
     , countStepInput = ""
     , countStepNum = 0
     }
+      ! []
```

シグネチャの変更は当然なのですが、 ` ! []` という部分を追加しました。これは `Cmd Msg` を追加するための便利な関数で、[次のようなシグネチャ](http://package.elm-lang.org/packages/elm-lang/core/5.1.1/Platform-Cmd)を持っています。

```hs
(!) : model -> List (Cmd msg) -> (model, Cmd msg)
```

`model` と `Cmd msg` のリストを受け取って、 `initModel` が返したい型である `(model, Cmd msg)` を返します。
今回は `List (Cmd Msg)` には空のリストを渡しておきましょう。

### `update` : 更新関数の変更

まずは `update` も `initModel` と同じように変更しましょう。

```diff
-update : Msg -> Model -> Model
+update : Msg -> Model -> (Model, Cmd Msg)
 update msg model =
     case msg of
         NoOp ->
-            model
+            model ! []

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

         UpdateCountStepInput s ->
-            { model | countStepInput = s }
+            { model | countStepInput = s } ! []

         UpdateCountStepNum num ->
-            { model | countStepNum = num }
+            { model | countStepNum = num } ! []
```

### `subscriptions` の追加

例によって呼び出されても何もしない関数を書いておきましょう。次回以降にでも使い方を解説していきます。

```hs
subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.none
```

### main 関数

最後に `main` 関数でまとめ上げます。

```diff
 main : Program Never Model Msg
 main =
-    Html.beginnerProgram
+    Html.program
         { init = initModel
         , update = update
         , view = view
+        , subscriptions = subscriptions
         }
```

さて、コンパイルするとアプリケーションが動くでしょうか？
追加した機能は一切ないので、機能は元のままのはずです。

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

## Cmd の作成

`beginnerProgram` のときは `Cmd msg` という型は出てきませんでしたが、実際にはElmが内部的に使っています。
Viewから `onClick (Increase 1)` などで発行していたアクションは、 `Cmd (Increase 1)` という形でデータ化されて `update` を呼び出す処理になります。
つまり、 `initModel` や `update` の返り値で `Cmd Msg` に何らかのアクションが指定するとそのアクションが実行されます。
ただし、 `Msg` と `Cmd Msg` は異なるため、 `model ! [ Increase 1 ]` などとしてもコンパイルエラーになります。
ではどうすれば良いのでしょうか？

### `Task` を評価して `Cmd` を得る

`Cmd msg` を作る方法は `Task` を作って `Task.perform` や `Task.attempt` を使うことです。
`Task` は非同期な処理などを記述して、その成否によってアクションを発行する基盤となるものです。

一例としてアプリケーションの初期化時に入力フォームの値を `5` にするアクションを発行してみましょう。

`Task` を利用するのに import が必要です。

```hs
import Task
```

`initModel` を次のように更新しましょう。

```diff
 initModel : ( Model, Cmd Msg )
 initModel =
     { count = 0
     , countStepInput = ""
     , countStepNum = 0
     }
-        ! []
+        ! [ Task.perform UpdateCountStepInput (Task.succeed "5") ]
```

`countStepInput = "5"` で初期化すれば良いのでは…と思う気持ちをぐっと堪えてください。

`Task.succeed "5"` は「必ず成功し `"5"` を返すタスク」を表現しています。その値を受け取って、 `Cmd (UpdateCountStepInput "5")` を返り値として返します。
これが実際に評価されることで、入力フォームの値が書き換わっているわけです。

これを確認するためには、DEBUGを開いて実行されているアクションを実際に確認すると良いです。
これまではクリックやキー入力に反応してアクションが実行されていましたが、アプリケーション起動時にいきなりアクションが実行されていることがわかります。

ココまでのコード -> https://ellie-app.com/4b78MtJKJZka1/4

## アクションの連鎖

それでは続いて、アクションを連鎖させてみましょう。
これまでは、

- フォームに数値を入力
- updateボタンを押す

の2アクションで増加する値を更新していました。これを1アクションにしてみましょう。

```diff
 update : Msg -> Model -> ( Model, Cmd Msg )
 update msg model =
     case msg of
         NoOp ->
             model ! []

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

         UpdateCountStepInput s ->
-            { model | countStepInput = s } ! []
+            { model | countStepInput = s } ! [ Task.perform convertInputToMsg (Task.succeed s) ]

         UpdateCountStepNum num ->
             { model | countStepNum = num } ! []
```

これで更新ボタンは不要になったので削除しましょう。

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

コンパイルして動作を確認してみましょう。
入力フォームに数値を入力すると自動的にカウンターの増加値が更新されているのが分かるでしょうか？

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

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

## まとめ

今回は複数のアクションが連鎖したり初期化時にアクションを発行するケースに対応するため、アプリケーションのフレームワークをより高度なものに変更しました。
次回は `subscriptions` も利用してバックグラウンドで自動実行されるタスクを作ってみましょう。