Elm初心者がElm 0.19でライフゲームを作ってハマったところ


Elm 0.19 に入門してみました。
前に参加した[Elm入門ハンズオン](https://elm-jp.connpass.com/event/98978/)で「Elmを勉強するならライフゲームを作るのが良いらしい」と聞いた気がしたのでライフゲームを作りました。

## 作ったもの


https://dreamy-jang-75f64b.netlify.com/

途中で止めたり、手動で次の世代を表示できたりします。

![life-game-animation.gif](https://qiita-image-store.s3.amazonaws.com/0/155890/fbc3e3f8-a6f1-dcd7-2099-c40d23a3fc3f.gif)

## Dependency

[Elm 0.19](https://elm-lang.org/)

## 実装

ソースコードはこちらです。
**https://github.com/amanoese/elm-life-game**

デザインはBootstrap ( [Elm Bootstrap](http://elm-bootstrap.info/) )を採用しました。

またライフゲームの描画にはSVG( [SVG in Elm](https://package.elm-lang.org/packages/elm/svg/latest/) )を採用しました。

Canvs([elm-canvas](https://package.elm-lang.org/packages/joakin/elm-canvas/latest/))を利用するのが楽そう？ですが、
調べたところ elm-canvas は何度か仕様が大きく変更されているらしく、
参考にできるコードが存在するのか不安だったため採用を見送りました。


## デプロイ(Netlify)

Netlify にデプロイします。
NetlifyでElmが直接サポートされているわけではありませんが、
Node.js と yarn がサポートされているため Elm のコードも Build＆Deploy できます。

packege.json の dependency に elm を登録します。
また、Build スクリプトを記載します。

```json:package.json
{
  ・・・省略
  "devDependencies": {
    "elm": "^0.19.0-bugfix2"
  },
  "scripts": {
    "build": "elm make src/Main.elm --output dest/index.html",
  }
}
```

あとは Netlify の Build Command に、``` yarn build ```を記載すればデプロイできました。
![Screenshot_2019-01-08 Build deploy Settings(1).png](https://qiita-image-store.s3.amazonaws.com/0/155890/5fb24dd6-dc09-817e-a4f7-d44a6293f9fe.png)

## ハマったところ

### Elm 0.19 の ```Browser.element``` について情報が少ない

副作用を伴う処理はElm 0.19では```Html.program```ではなく```Browser.element```を利用する必要があります。
一応解説してくれている方がいるのですが、初心者なのでハマりました。

https://qiita.com/jinjor/items/245959d2da710eda18fa

Elm 0.18 で使われていた```Html.program```は以下のような型です。

```elm
program :
    { init : ( model, Cmd msg )
    , view : model -> Html msg
    , update : msg -> model -> ( model, Cmd msg )
    , subscriptions : model -> Sub msg
    }
    -> Program Never
```
Elm 0.19での```Browser.element```は、以下のような型になっています。

```elm
element :
    { init : flags -> ( model, Cmd msg ) -- ← ここが異なる
    , view : model -> Html msg
    , update : msg -> model -> ( model, Cmd msg )
    , subscriptions : model -> Sub msg
    }
    -> Program flags model msg
```

以前の```Html.program```ではinitが値だったのですが、```Browser.element```では関数になっており引数が必須になっています。*

初期化するための関数initを定義しないといけないのですが、
私は、```Browser.sandbox```で大枠を実装した後に```Browser.element```に移行しようしたため下記のようなエラーを出してしまいました。

```txt:Error
-- TYPE MISMATCH -------------------------------------------------- src\Main.elm

The 1st argument to `element` is not what I expect:

133|   Browser.element  { init = init , update = update, view = view ,subscriptions = subscriptions }
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This argument is a record of type:

    { init : ( Model, Cmd Msg )
    , subscriptions : Model -> Sub Msg
    , update : Msg -> Model -> ( Model, Cmd Msg )
    , view : Model -> Html Msg
    }

But `element` needs the 1st argument to be:

    { init : flags -> ( Model, Cmd Msg )
    , subscriptions : Model -> Sub Msg
    , update : Msg -> Model -> ( Model, Cmd Msg )
    , view : Model -> Html Msg
    }
```

いや、わかってますよ…… 
この型引数 ```flag``` は下記のような初期化したいときのためにjavascript側から値を渡すために用意されてる引数らしいです。

```javascript:sample.js
Elm.Main.init(0);
```

```elm:sample.elm
init : Int -> model
init num =  ( {num = num} , Cmd.none)
```

今回のアプリケーションでは初期値は不要なので引数を渡さない方法が知りたかったのですが、
1～2時間悩んだ結果下記のようなコードでいけるとわかりました。

```elm
init : () -> model
init _ = ( { num = 0 } , Cmd.none )
```
入力が存在しない場合は空タプルを渡すとよいのでしょうか？

### Random(副作用) 難しい問題


```Msg``` のパターンマッチを利用が必要になることは、解説無しには理解できなかったと思います。
下記の記事がとても参考になりました。@sandさんに感謝です。

https://qiita.com/sand/items/4efd8eeafd2c33778e08

それでも、ネストしたリストにランダムな値を与える綺麗な方法がわからなかったため、
今回は下記のように、一度ランダムな数をすべて生成した後、分割するようにして解決しています。

```elm:random-sample.elm
type Msg
  =  Init
  | RandomList (List Int)

init : () -> (Model, Cmd Msg)
init _ =
  ( { cells = initCells 0 , numberOfCells = 30 } ,Cmd.none)

update: Msg -> Model-> (Model , Cmd Msg)
update msg model =
  let { cells, numberOfCells } = model
  in
  case msg of
    Init ->
      (model, Random.generate RandomList (Random.list (numberOfCells ^ 2) (Random.int 0 1))) --フラットなrandamのintのリスト
    RandomList randomInts ->
      ( { model | cells = flattenCells <| split numberOfCells randomInts } -- リストを n * n のネストしたリストに分割
        , Cmd.none
      )
```

### なんか遅い

最初は List だけで実装していたのですが、パフォーマンスが悪すぎて調べたところ実装がLinked-Listらしいです。
下記の記事が参考になりました。しかし、下記の記事は古いため直和型の部分については無視したほうが良い？と思います。

https://qiita.com/philopon/items/1a74e434520a06327064

Listで書いていた部分をDictで書き直すと爆速になりました。@philoponさんに感謝です。

### subscriptionsについて
Randomを理解した後だとハマらずに実装できました。

## 感想
今の知識でスクラッチしたらもっと綺麗に書けるなーと思うほど結構勉強になりました。

「Elmが凄い言語だよ！」って話は3~4年前から聞いていたのですが、
純粋関数型言語でフロントエンドが実装できるって触れ込みの誇大広告ぽさと、
若い言語で廃れていくんだろうな……という理由から勝手に避けていました。

しかし、2019年になった今でも人気のある言語の一つであり、
[Elm入門ハンズオン](https://elm-jp.connpass.com/event/98978/)に参加した結果かなり実践的な言語と実感しました。

Elm楽しいですね。