初心者が Elm 0.19 で 2048 を作るまで Part 1

## 動機

JavaScript のしがらみから一切離れて書ける（らしい） Elm という言語に興味を持ったので、練習として 2048 を作ります。使ってみた上で、今後どのように使っていけそうな言語かを考えます。

前提として、自分は JavaScript は趣味で書く程度ですが関数型言語の経験があまりないです。入門時に自分が得た印象を残すという意味でも、似たような状況の人に例を示すという意味でも、とりあえず記録しておきます。


## 環境
- Windows
- Elm 0.19.0


## 第０段階　インストール

[公式サイト](https://elm-lang.org/) の [Install] を参考にします。npm があれば `npm i elm` するか、Windows か Mac の場合はインストーラをダウンロードできるそうです。


## 第１段階　挨拶

まず作業ディレクトリを用意し、コマンドプロンプトでそこまで移動します。

```
> mkdir elm-2048
> cd elm-2048
```

ディレクトリ内で以下のコマンドを叩きましょう。

```
> elm init
Hello! Elm projects always start with an elm.json file. I can create them!

Now you may be wondering, what will be in this file? How do I add Elm files to
my project? How do I see it in the browser? How will my code grow? Do I need
more directories? What about tests? Etc.

Check out <https://elm-lang.org/0.19.0/init> for all the answers!

Knowing all that, would you like me to create an elm.json file now? [Y/n]:
```

`elm.json` という、設定を記述するファイルを作るか聞かれます。（package.json みたいなものでしょうか。v0.18 までは `elm-package.json` という名前だったようです。）`Y` と答えておきましょう。

```
Okay, I created it. Now read that link!
```

さて、ディレクトリ内にファイル `elm.json` とフォルダ `src` ができました。最初にやることといえば世界に挨拶をすることなので、`src` 内に `Main.elm` を作ってみましょう。（名前はなんでもいいです）

```elm:Main.elm
import Html exposing (..)

main =
    text "Hello, world!"
```

ファイルができたら、コマンドで

```
> elm reactor
```

と打ちます。

```
Go to <http://localhost:8000> to see your project dashboard.
```

言われるがまま http://localhost:8000 を開くと

![image.png](https://qiita-image-store.s3.amazonaws.com/0/62215/e703ced2-8012-f770-52dc-3c996a4f3492.png)

すごい！　こんな画面が出ました。`src` > `Main.elm` に移ってみると

![image.png](https://qiita-image-store.s3.amazonaws.com/0/62215/8166721c-3819-d6b1-f90d-fc9dcf41c5de.png)

成功です。以下これを開きながら確認していきます。

[この時点でのコミット](https://github.com/n4o847/elm-2048/tree/78f6d7636621ebc447114a26d1ba0cbe60f4b2e7)


## 第２段階　カウンター

公式のガイドとかを見ると、`view` とか `update` とかで分離されているので、やってみます。Elm には初心者が色々いじるための `Browser.sandbox` という関数が用意されています。

（ググると `main = Html.begginerProgram ...` としている例がありますが、[v0.19 から変更になりました](https://github.com/elm/compiler/blob/master/upgrade-docs/0.19.md)。）

とりあえず [ガイド](https://guide.elm-lang.org/architecture/buttons.html) を参考に `Main.elm` をいじってみます。

```elm:Main.elm
import Browser
import Html exposing (..)
import Html.Events exposing (..)


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


type alias Model =
    Int


init : Model
init =
    0


type Msg
    = Increment
    | Decrement


update : Msg -> Model -> Model
update msg model =
    case msg of
        Increment ->
            model + 1

        Decrement ->
            model - 1


view : Model -> Html Msg
view model =
    div []
        [ h1 [] [ text "Hello" ]
        , div [] [ text (String.fromInt model) ]
        , button [ onClick Decrement ] [ text "-" ]
        , button [ onClick Increment ] [ text "+" ]
        ]
```

型注釈は別にいらないと思うけど勉強のためにつけてみました。

なるほど、Elm の考え方が少し見えてくる気がします。

ブラウザで確認すると

![image.png](https://qiita-image-store.s3.amazonaws.com/0/62215/eefdc203-a38c-5593-9087-d7306dff0d4b.png)

良さそうですね。ここで F12 で開発者ツールを開いてボタンをポチポチしてみると、数値の含まれる `<div>` だけが変化していることがわかり、「あー Virtual DOM なんだな」と実感できます。

[この時点でのコミット](https://github.com/n4o847/elm-2048/tree/724867abbec9af85c4a80dde11c3942ba147be19)


## 第３段階　盤面の表示

さてここからは作りたいものを作るため、ガイドやドキュメントとの闘いになります。（実は「elm 2048」とかで調べると先人のレポジトリが色々出てきますが、自力でやりたい＆バージョン毎の差異が比較的大きいので、完成するまで見ないことにします。）

まずボードを表示させましょう。データ構造としてデフォルトでは `List` が使われますが、座標でアクセスしたいので `Array` の方が良さそうです。

```elm
import Array exposing (Array)
```

`Model` 周辺を書きかえて

```elm
type alias Model =
    { board : Board
    }

type alias Board =
    Array Row

type alias Row =
    Array Cell

type Cell
    = Tile Int
    | Empty


init : Model
init =
    { board =
        Array.repeat 4 <| Array.repeat 4 <| Tile 2
    }
```

`view` 周辺も書きかえて表示関数を作ります。

```elm
viewBoard : Board -> Html Msg
viewBoard board =
    div []
        <| Array.toList (Array.map viewRow board)


viewRow : Row -> Html Msg
viewRow row =
    div []
        <| Array.toList (Array.map viewCell row)


viewCell : Cell -> Html Msg
viewCell cell =
    case cell of
        Tile number ->
            span []
            [ text (String.fromInt number)
            ]

        Empty ->
            span [] []
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/62215/9450e71c-ad5f-2ce9-5780-782537529ba9.png)

ちゃんと表示されています。もう少し良い書き方があるのかもしれないけどとりえずこれで。

[ここまでのコミット](https://github.com/n4o847/elm-2048/tree/8e72287ad2938cb78b9536273920a91931a0074c)

ではセルを変更するようにしてみましょう。

```elm
setBoard : Int -> Int -> Cell -> Board -> Board
setBoard i j cell board =
    case Array.get i board of
        Just row ->
            let
                newRow = Array.set j cell row
            in
                Array.set i newRow board

        Nothing ->
            board
```

うーん、これはちょっと手続き的では、と思い、時間が立って書き直したコードが

```elm
type alias Position =
    ( Int, Int )


setBoard : Position -> Cell -> Board -> Board
setBoard (i, j) cell board =
    Array.get i board
        |> Maybe.map (\oldRow -> Array.set j cell oldRow)
        |> Maybe.map (\newRow -> Array.set i newRow board)
        |> Maybe.withDefault board
```

うーん関数型の気持ちがわからない……。まあさっきよりマシっぽいのでいいでしょう。

さて、適当に変更させてみます。

```elm
update msg model =
    case msg of
        Change ->
            { model |
                board = setBoard (1, 1) (Tile 4) model.board
            }
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/62215/d1a9d5b7-6657-a99c-6685-750900cf375d.png)

ボタンを押すとセルが変わりました！

……実は内心、このコードで盤面が

```
2 4 2 2
2 4 2 2
2 4 2 2
2 4 2 2
```

のようになることを期待していました（JavaScript で同様に書くとそういう挙動になるはずなので）が、そうはならなくて感動しました。永続データ構造しゅごい……。ちなみに `Array` の実装は内部的には32分木になっているそうです。

加えて、ここまで何回もエラーを出して怒られましたが、コンパイルの時点でそれだけ面倒を見てくれるということなのでありがたいことです。

[ここまでのコミット](https://github.com/n4o847/elm-2048/tree/8a71de703ec23950bb84a5b89134d86c6bc80476)


## 第４段階　キー入力

次はキー入力ですが、[v0.19 でキー入力の扱いが変わった](https://github.com/elm/compiler/blob/master/upgrade-docs/0.19.md)っぽいです。

現時点での `Browser.Events` のドキュメントを読むと、`onKeyDown : Decoder msg -> Sub msg` となっています。`Sub` とかいうのが出てきたので、とりあえず `Browser.sandbox` から脱却する必要がありそうです。

```diff
 main : Program () Model Msg
 main =
-    Browser.sandbox
+    Browser.element
         { init = init
         , view = view
         , update = update
+        , subscriptions = subscriptions
         }
```

各所の `Model` が返る部分も `( Model, Cmd Msg )` になるよう置き換えます。

さらに必要なモジュールを入れます。

```elm
import Browser.Events exposing (onKeyDown)
import Json.Decode as Decode
```

https://github.com/elm/browser/blob/1.0.0/notes/keyboard.md を参考に、キー入力の読取り器を書きます。

```elm
type Direction
    = Left
    | Right
    | Up
    | Down
    | Other


keyDecoder : Decode.Decoder Direction
keyDecoder =
    Decode.map toDirection (Decode.field "key" Decode.string)


toDirection : String -> Direction
toDirection string =
    case string of
        "ArrowLeft" ->
            Left

        "ArrowRight" ->
            Right

        "ArrowUp" ->
            Up

        "ArrowDown" ->
            Down

        _ ->
            Other
```

`elm/json` が必要とかいわれるので入れます。

```
> elm install elm/json
```

https://github.com/elm/browser/blob/master/examples/wasd.elm を参考に `subscription` や色々を書き足します。

```elm
type Msg
    = Change Direction


subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.batch
        [ onKeyDown (Decode.map Change keyDecoder)
        ]
```

あとは適宜 `update` をいじって、どのキーを押したかによって表示を変えてみます。ちゃんと動きますね。

[ここまでのコミット](https://github.com/n4o847/elm-2048/tree/e47d9bac35b8ab309f3e4b12325191cef549dd58)


## 続き

結構長くなってしまったので、続きは次回にします。

[Part 2](https://qiita.com/n4o847/items/023575f10875b1e3e182)