Elmハンズオン その1

（注）このハンズオン資料は記載時点の最新版 Elm 0.19 を対象としています。

## ブラウザだけでElmをはじめる

Elm を体験するだけなら、ローカルマシンにインストールは必要ありません。

[Ellieを開く](https://ellie-app.com/new)  ※初回アクセス時は利用規約への同意(ACCEPT TERMS)を行ってください

EllieはElmコードをコンパイルしてプレビューしてくれるオンラインサービスです。
今回のような体験にも使えますし、外部パッケージをインストールすることもできるのでちょっとした動作確認に使えます。

### Ellie の画面構成

![Ellieの画面構成](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14950/2b723c22-318c-a84b-0cf9-fc2dba73a641.png)

- Elmのコードを記述する部分
- HTMLのコードを記述する部分
- プレビューを表示する部分

に分かれています。今回のハンズオンではHTMLを記述する部分は変更しませんので、HTML記述部分の右上の矢印で閉じておいても良いでしょう。

### (optional) フォントを変えても良い

左上の歯車⚙アイコンで見た目などの変更ができます。フォント（サイズや種類）はご自身で使いやすいものを選択しましょう。

![Ellie Settings](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14950/ad8a5a0a-7c27-af12-bfeb-656a729b9168.png)

## 最初に表示されるサンプル

Ellieの初期画面の状態でコンパイルしてプレビューが表示されている状態です。

Ellieで最初に表示されるのはシンプルなカウントアップ・カウントダウンができるアプリケーションです。
初期状態は数値が 0 になっており、+1ボタンをクリックすると値が増え、-1ボタンをクリックすると値が減ります。
ボタンをポチポチ触ってみましょう。

プレビュー部分のブラウザの上部にある「RELOAD」ボタンを押してみましょう。ブラウザがリロードされる挙動を再現しており、カウントが0に戻ります。

## サンプルコードをコンパイルする

サンプルコードをすこしいじってみてコンパイルしてみましょう。

まずは、 `initialModel` を変更し、 `count = 100` にしてみます。

```elm
initialModel : Model
initialModel =
    { count = 100 }
```

この時点では自動的にはコンパイルされません。
「COMPILE」ボタンを押してコンパイルしてみましょう。

するとプレビューが更新されて表示される値が100になったはずです。
RELOADを押しても最初に表示される状態が 100 になっています。

### Hello, world

定番ネタの `Hello, world!` を表示してみましょう。

![Hello, world!](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14950/75d53afc-cb16-ffc8-1185-a4490c77b60b.png)

さて、どこをいじればこのような表示ができるでしょうか？
画面表示に関する部分がどこにあるのか、推測してみてください。

---- 

答えは `view` の部分になります。次のようにコードを変更してみましょう。

```elm
view : Model -> Html Msg
view model =
    div []
        [ button [ onClick Increment ] [ text "+1" ]
        , div [] [ text <| String.fromInt model.count ]
        , button [ onClick Decrement ] [ text "-1" ]
        , div [] [ text "Hello, world!" ]
        ]
```
(Elmで推奨されるコードフォーマットはやや見慣れないかもしれません。右上にフォーマットボタンもあるのでぜひ使ってみてください）

`view` という名前なので追記する場所についてはわかりやすいかもしれませんが、追加すべきコードはまだわからないかもしれません。
追加した部分 `div [] [ text "Hello, world!" ]` で生成されるHTMLはこのようになります。

```html
<div>Hello, world!</div>
```

記述方法は違いますがなんとなくHTMLを記述していることは納得できるでしょうか。

## 挙動を変えてみる

ここまで、初期状態(`initialModel`)を変えて、HTMLの記述(`view`)を変えました。
続いてアプリケーションの動作を変更してみましょう。

ひとまず、 +1 を +2 にしてみたいと思います。

```elm
view : Model -> Html Msg
view model =
    div []
        [ button [ onClick Increment ] [ text "+2" ]
        , div [] [ text <| String.fromInt model.count ]
        , button [ onClick Decrement ] [ text "-1" ]
        , div [] [ text "Hello, world!" ]
        ]
```

画面上は +2ボタンになりました。しかし、挙動は相変わらず +1 のままです。

![add 2 view only](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14950/21da798f-3154-a3d6-8e6b-922f2d479071.png)

実際の挙動を管理しているのは `update` の部分です。
値を修正してみましょう。

```elm
update : Msg -> Model -> Model
update msg model =
    case msg of
        Increment ->
            { model | count = model.count + 2 }

        Decrement ->
            { model | count = model.count - 1 }
```

コンパイルすると値が正しく +2 されるようになりましたか？


## 機能を追加してみる

ここまでは既存のコードの値を変更するだけでした。
次はボタンを追加して機能を増やしてみましょう。

値をリセットするのに毎回RELOADを実行するのは不便ですよね。
そこで、リセットボタンを追加してみることにします。

### リセットボタンを表示する

まずはボタンを表示します。既存のボタンを真似て、コードを追加してみましょう。
なお、十分役目を終えた `Hello, world!` には消えてもらうことにします。

```elm
view : Model -> Html Msg
view model =
    div []
        [ button [ onClick Increment ] [ text "+2" ]
        , div [] [ text <| String.fromInt model.count ]
        , button [ onClick Decrement ] [ text "-1" ]
        , button [] [ text "reset" ]
        ]
```

修正後にコンパイルすると、ややボタンの位置が不格好ではあるものの、HTMLの記述通りの状態になります。
まずは機能が動作することを確認したいので、このまま進めます。

![Ellie_-_Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14950/98cebb98-f83e-403e-bec6-6047ad2c7317.png)

このボタンは表示上追加しただけなので、ボタンを押してもリセットはされません。

### 「リセットする」指示を定義する

「リセットする」というアプリケーションの挙動を定義します。

まずは、アプリケーション上の指示を `Msg` に追加します。
この `Msg` は代数型と呼ばれ、 `Increment`, `Decrement`, `Reset` という定数のどれかになる（それ以外はありえない）ことを定義しています。

```elm
type Msg
    = Increment
    | Decrement
    | Reset
```

この段階でコンパイルしてみましょう。コンパイルエラーが発生するはずです。

![Ellie Compile Error 1](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14950/aab4c287-78ec-e996-d3c7-6ed07945f5c6.png)

Elm の出力するエラーは非常に丁寧です。エラーが発生した場所だけでなく、どのような修正を行うべきかまである程度教えてくれます。
今回の場合、 `Msg` の種類を増やしたのに、 `Msg` を扱っている `update` の方では `Increment` と `Decrement` の２パターンのみに対応する方法しか記載していないので、 `Reset` に対応する部分が抜けていますよ、ということを示しています。

では、 `update` を `Reset` に対応させましょう。
「初期状態に戻す」ということはどのように記述すれば良いでしょうか？

すぐに思いつく方法は、他の例に倣って `count = 100` と記述する方法だと思います。

```elm
update : Msg -> Model -> Model
update msg model =
    case msg of
        Increment ->
            { model | count = model.count + 2 }

        Decrement ->
            { model | count = model.count - 1 }
            
        Reset ->
            { model | count = 100 }
```

人によっては 100 を定数化したい、などイライラするかもしれませんが、ひとまずはこれで大丈夫です。
まだこの `{  |  }` という記法の読み方も記載していませんので…。

ひとまずこれでコンパイルエラーは解消されました。しかし、まだボタンは動作しません。

### ボタンの挙動に機能を設定する

最後に、ボタンを押したら機能を呼び出すようにしてみましょう。
再び `view` の方を修正します。

```elm
view : Model -> Html Msg
view model =
    div []
        [ button [ onClick Increment ] [ text "+2" ]
        , div [] [ text <| String.fromInt model.count ]
        , button [ onClick Decrement ] [ text "-1" ]
        , button [ onClick Reset ] [ text "reset" ]
        ]
```

`onClick Reset` を追加しました。 
これは、`<button onClick="...">` に相当するものですが、JavaScriptでは「処理」を記述するのに対して、 Elm では `Reset` という値を送っています。

ここまでのコードはこちらからも参照できます -> https://ellie-app.com/nhq5QTrNMpHa1

## Elm の動作する仕組み

ここまで部分的に変更してきましたが、それぞれの部分がどのようなつながりを持って動いているのか理解しておきましょう。
Elm は [The Elm Architecture](https://guide.elm-lang.org/architecture/) と呼ばれるアプリケーションの骨組みを持っています。
これは基本的に3つのものから成り立っていて、

- Model : アプリケーションの持つ内部状態を表現する
- View : ModelからHTMLを生成する関数
- Update : Msg を受け取って、Modelを更新する関数

だけです。

![20161129180930.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14950/7999935e-0f59-594c-9180-a5b336325307.png)

ここで、「Modelを更新する」とサラッと書きましたが、ここが関数型言語の気にすべきポイントです。
Elmでは全ての値は不変(immutable)であるという特徴があるため、実際には「更新している」のではなく「新しいModelの値を作っている」ということになります。

## レコード

`Model` は次のように定義されています。

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

`type alias` とは型の別名を定義する文法です。ここでは `=` の右である `{ count : Int }` という型に `Model` という別名を定義しています。

では、 `{ count : Int }` は何かというと「レコード」という型です。JavaScriptでいうオブジェクトに近いです。
レコードには `count` という「フィールド」が宣言され、中身は `Int` 型のデータが入ることが定義されています。

レコード型の値は `initialModel` で作っています。

```elm
initialModel : Model
initialModel =
    { count = 100 }
```

定義時は `{ count : Int }` のようにコロンで定義していましたが、作るときは `{ count = 100 }` とイコールになることに注意しましょう。

### レコードの更新

先程のリセットの処理では `count` の値を設定するだけなので、次のように記述しても動作します。

```elm
        Reset -> 
            { count = 100 }
```

しかし、この方法ではもしフィールドが複数ある場合に面倒なことが起こります。
すでに述べたように Elm では既存のmodelの値やフィールドの変更はできず、「新しい値」として生成する必要があるので変更しないフィールドまで全て列挙しなくてはならず大変なことになります。

```elm
{ count = 100, hoge = model.hoge, fuga = model.fuga, ... }
```

そこで、レコードの更新には便利な方法があります。「フィールドアクセス関数」と呼ばれる便利な記法です。
ベースとなるレコードを `|` の左に置き、右には変更したい部分のフィールドの値を記述します。

```elm
{ model | count = 100 }
```

これでフィールドが増えても部分更新がしやすいので助かりますね。

## デバッグモードを見てみる

値が不変であり、新しいModelの値を毎回生成していることを少し実感してみましょう。
Elmにはデバッグモードでのビルドが標準で存在します。 Ellie 上では右上に DEBUG ボタンがあるので開いてみましょう。

![Ellie_DEBUG.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14950/c74a125e-a34f-3cbb-ae76-b255e517c581.png)

デバッグモードではMsgが発生するごとに履歴が一つずつ生成されており、そのMsgを処理した状態のModelに戻すことができます。
このように毎回Modelを新しいModelの値に置き換えることでアプリケーションが動作していることがわかります。

## 型・シグネチャ

さて、少し関数型言語の側面を紹介していきます。
これまで説明をしていなかった `main` 関数を見てみましょう。

```elm
main : Program () Model Msg
main =
    Browser.sandbox
        { init = initialModel
        , view = view
        , update = update
        }
```

`Browser.sandbox` というのは The Elm Architecture のためのエントリーポイントの中で一番シンプルなものです。ここでは詳しく説明しませんが、他にも `Browser.element`, `Browser.document`, `Browser.application` などがあり、より高機能なアプリケーション開発を行う際は必要になります。

[Browserのドキュメント](https://package.elm-lang.org/packages/elm/browser/latest/Browser)

`Browser.sandbox` も関数です。公式ドキュメントを見ると次のように記載されています。

```elm
sandbox :
    { init : model
    , view : model -> Html msg
    , update : msg -> model -> model
    }
    -> Program () model msg
```

これまでの `view` や `update` のような関数の1行目にもありましたが、こちらが「型シグネチャ」と呼ばれるものです。
`sandbox` は「 `init`, `view`, `update` というフィールドを持つレコード」を受け取って、「 `Program () model msg` 型の値」を返します。

最初の Model で見たレコードの定義ではフィールドが `count : Int` だったので整数値でしたが、 `view : model -> Html msg`  というように、ここにもシグネチャのようなものがあります。
これはすなわち「関数」を値として受け取ることを意味しています。
このように、「関数を値として扱うこと」を **第一級関数(first-class function)** と言います。関数型言語ではこれが前提となる考え方の一つとなります。

### 関数を返す関数

では、これまで見てきた `initialModel` , `view` , `update` の型シグネチャを見てみましょう。

```elm
initialModel : Model
view : Model -> Html Msg
update : Msg -> Model -> Model
```

さきほどの `sandbox` のシグネチャとは大文字小文字が違いますが、小文字で定義したほうは「 `model` の部分には同じ型が入る」というもので「型引数」といい、大文字で始まる `Model` が具体的に定義されたものです。

`update` のシグネチャは矢印 (`->`) が２つあります。これは Elm 上はこのように解釈されます。

```elm
update : Msg -> ( Model -> Model )
```

`Model -> Model` は `Model` を受け取って `Model` を返す関数を表しています。
つまり、 `update` 関数は `Msg` を受け取って、「モデルの値を新しいモデルにして返す関数」を返り値として返していることになります。

ところが、現在の `update` 関数は２つの値を受け取るかのように記述されています。

```elm
update : Msg -> Model -> Model
update msg model =
    ...
```

このように2引数の関数のように定義することもできます。

ちょっと回りくどくなりますが、今回はこれを実際に「関数を返す関数」に分解してみましょう。
まず、3つの関数を定義します。

```elm
increment : Model -> Model
increment model =
    { model | count = model.count + 2 }
    
decrement : Model -> Model
decrement model =
    { model | count = model.count - 1 }
    
reset : Model -> Model
reset model =
    { model | count = 100 }
```
これらはすべて `Model -> Model` の型シグネチャを持っています。

続いて、 `update` 関数を変更してみましょう。

```elm
update : Msg -> Model -> Model
update msg =
    case msg of
        Increment ->
            increment 

        Decrement ->
            decrement
            
        Reset -> 
            reset
```

引数が `msg` だけになり、 `Msg` の各パターンに応じて関数を返すようになりました。
これでも正しく動作することを確認しましょう。