Elm 0.18 入門 (1)カウントアプリを作ってみる

[関数型フロントエンド言語 Elm](http://elm-lang.org/) 0.18 のチュートリアルです。

- ブラウザだけで開発を始める
- シンプルなモデルの定義
- モデルの状態を画面に表示する
- アクションを定義してモデルの状態を更新する

## WebでElmをはじめる

[Ellie](https://ellie-app.com) というオンラインエディタでElmを始めてみましょう。こちらはElmコードとそれを埋め込むためのHTMLコードをそれぞれ編集したり、Elmのパッケージを追加して開発することができます。

<img width="1565" alt="Ellie画面" src="https://qiita-image-store.s3.amazonaws.com/0/14950/85b399c5-a98f-7370-491d-5e2b03c67e9e.png">

最初に書いてあるコードはElm側はテキストを表示するだけです。

```hs
module Main exposing (..)

import Html exposing (Html, text)


main : Html a
main =
    text "Hello, World!"
```

一方HTML側では、Elmの呼び出しとCSSの記述があります。

```html
<html>
<head>
  <style>
    html {
      background: #F7F7F7;
      color: red;
    }
  </style>
</head>
<body>
  <script>
    var app = Elm.Main.fullscreen()
  </script>
</body>
</html>
```

文字が全部真っ赤だと辛いので、 `color: red;` だけ消しておきましょう。

```diff
 html {
   background: #F7F7F7;
-  color: red;
+  /*color: red;*/
 }
```

コンパイルを実行すると反映されます。
以降のコードではHTML側は編集しません。

## モデルを作って画面に表示する

では、アプリケーションの作成をしてみましょう。
Elmはアプリケーションの状態である「データモデル」を中心に、基本的に2つの処理を書きます。

- データモデルをHTML表示にする(View)
- データモデルをアクションに合わせて更新する(Update)

いきなり動きのあるアプリケーションを作る前に、まずはModelとViewから始めてみましょう。

アプリケーション全体のモデルとなる `Model` 型を作ります。 `Int` そのものでも良いのですが、名前をつけてカウントを管理してみましょう。初期値は `0` としています。

```hs
type alias Model =
    { count : Int
    }

initModel : Model
initModel =
    { count = 0
    }
```

`type alias Model` が型の定義部分で、 `initModel : Model` は Model型の初期値を返す関数です。後ほど使います。

次に、先程は作らないと言っていた `update` を作ります。ただし、定義するアクションは `NoOp` (No Operation, 何もしない)のみです。

```hs
type Msg
    = NoOp

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

内容は後述しますが、ここでは何もしないことだけを定義しています。

続いて `view` の定義です。ElmではHTMLの記述も行います。

```hs
view : Model -> Html Msg
view model =
    p []
        [ text "count: "
        , text (toString model.count)
        ]
```

`view : Model -> Html Msg` はあるモデルの状態を受け取ってHTMLを返す関数で、
`p` はHTMLタグ `<p>` に対応しており、属性のリスト `[]` とタグの内容 `[ text ... ]` の2つを受け取って`Html` を返す関数です。

ただ、この状態ではコンパイルエラーが発生します。理由は、 `p` がインポートされていないためです。 `import` を修正しましょう。

```diff
-import Html exposing (Html, text)
+import Html exposing (Html, text, p)
```

最後に、アプリケーションとして機能するように関数を組み立てます。既に `main` 関数があると思いますが、一旦削除して次のように変更して下さい。

```hs
main : Program Never Model Msg
main =
    Html.beginnerProgram
        { model = initModel
        , update = update
        , view = view
        }
```

コンパイルしてプレビューに「count: 0」と表示されることを確認しましょう。
ここまでの全体像は次のようになります。

https://ellie-app.com/4494JhJ7PcJa1/3

```hs
module Main exposing (..)

import Html exposing (Html, text, p)


main : Program Never Model Msg
main =
    Html.beginnerProgram
        { model = initModel
        , update = update
        , view = view
        }


type alias Model =
    { count : Int
    }


initModel : Model
initModel =
    { count = 0
    }


type Msg
    = NoOp


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


view : Model -> Html Msg
view model =
    p []
        [ text "count: "
        , text (toString model.count)
        ]
```

初期値を変更してみたりviewの内容をいじって再コンパイルしてみてください。

## カウントアップを実装する

ボタンを押すとカウントアップする機能を実装してみましょう。

まず、アクションの種類を追加します。

```hs
type Msg
    = NoOp
    | Increase
```

`type Msg = NoOp | Increase` という表記は、「 `Msg` 型の値は `NoOp` または `Increase` のどちらかである」ということを定義しています。 `NoOp` などの具体的な定義がない、と思う方もいるかもしれませんが、 `NoOp` はそれ自体がコンストラクタの役割も持っており、 `Msg` 型の値そのものです。
バックグラウンドを詳しく知りたい方は[代数的データ型](https://ja.wikipedia.org/wiki/%E4%BB%A3%E6%95%B0%E7%9A%84%E3%83%87%E3%83%BC%E3%82%BF%E5%9E%8B)などを参照して下さい。

続いて、アクションへの具体的な対応（モデルの更新）を実装します。

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

`case msg of` は「パターンマッチ」です。ここでは `Msg` の具体的な内容に応じて場合分けされていて、 `NoOp` の場合は現在のmodelをそのまま返し、 `Increase` の場合はモデルの更新を行っています。
`{ model | ... }` はモデルの一部分だけを更新した新しいモデルを作る構文です。ここでは `count` を現在のモデルのカウント `model.count` に `+ 1` した新しいモデルを返しています。


この状態でコンパイルすることができますが、肝心の `Increase` アクションを起こす方法を定義していません。
`view` にボタンを作ってカウントアップできるようにしてみましょう。

```hs
view : Model -> Html Msg
view model =
    div []
        [ counter model
        , increaseButton
        ]


counter : Model -> Html Msg
counter model =
    p []
        [ text "count: "
        , text (toString model.count)
        ]


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

先程までの `p` をカウンター表示用の関数 `counter` から返すようにして、 `increaseButton` を追加しました。 `increaseButton` はモデルの状態によって変更する部分はないので `Model` を引数に含めていません。

最後に、`div`, `button` タグやイベントを実行するための `onClick` をインポートしましょう。

```diff
-import Html exposing (Html, text, p)
+import Html exposing (Html, text, p, div, button)
+import Html.Events exposing (onClick)
```

それではコンパイルして確認してみましょう。上手くカウントアップできているでしょうか？

ここまでのコードは以下で確認できます。

https://ellie-app.com/4494JhJ7PcJa1/4

## 状態のリセットとデバッグ

このアプリケーションにはブラウザなどに状態を記録してはいないので、アプリケーションをリロードするとカウントが初期値に戻ります。プレビュー画面右下の「RELOAD」をクリックして確認してみましょう。

また、Elm 0.18では標準でデバッグモードを搭載しており、Eliieでも確認することができます。プレビュー画面左下の「DEBUG」をクリックしてみましょう。

<img width="743" alt="Debugger" src="https://qiita-image-store.s3.amazonaws.com/0/14950/b1ddeae3-3154-75b0-1250-776bcf91a43e.png">

アクションの履歴と、そのアクションが実行されたあとのモデルの状態が記録されており、クリックするとそのモデルの状態を画面で再現してくれます。
現在のモデルの状態に戻るには「Resume」をクリックします。

## 宿題

- カウントダウン
- "+5" ボタン

などを実装してみましょう。カウントダウンでマイナス値にならないように制御するにはどうすればよいでしょうか？

## まとめ

[Elm Architecture](https://guide.elm-lang.org/architecture/)の一番簡単なモードである `beginnerProgram` で小さなアプリケーションを作ってみました。いかがだったでしょうか？
モデルの定義とモデルを更新するためのアクション、そしてモデルの表示方法にきちんと分離されていてきれいな仕組みだと思います。

気になった方はぜひ自分なりのサンプルアプリケーションを作ってみて下さい。

次回: [Elm 0.18 入門 (2)パラメータ付きのアクションと入力フォーム](http://qiita.com/mather314/items/4dc12c418e80b7a8373e)