elmで対局タイマー作ってみた

Elm入門がてら、最低限動くアプリケーション作ってみました。
それがこちら

![output.gif](https://qiita-image-store.s3.amazonaws.com/0/47896/79b2e8d0-8514-d665-814f-8ab1ac329cbc.gif)

ユースケースとしては、思考系の対戦で
消費時間を測りながらしたいときに使えればなと。

[app page](https://smith-30.github.io/elm-bt/)
[repository](https://github.com/smith-30/elm-bt)

### モチベーション

- [Elm-jp](https://qiita.com/organizations/elm-jp)に一応入れてもらっているので何かしらアウトプットだしたい
- [Elm入門ハンズオン](https://elm-jp.connpass.com/event/98978/)で実際に手を動かして面白かったので熱が冷めないうちに勉強しときたい

[Elm入門ハンズオン](https://elm-jp.connpass.com/event/98978/) には当日使った資料も公開されているので開発環境整えたい方の参考になると思います

### コード

これだけ、とてもシンプル。
僕が初心者なので上級者はもっとシンプルに書くのだろうと思いますが
それでも100行程度で済みました。

```elm:Main.elm

module Main exposing (Model, Msg(..), init, main, subscriptions, update, view)

import Browser
import Html exposing (Html, div, h1, img, input, text)
import Html.Attributes exposing (class, src, type_, value)
import Html.Events exposing (onClick)
import Task
import Time



-- MODEL


type alias Model =
    { zone : Time.Zone
    , time : Time.Posix
    , counter : Int
    , limit : Int
    , isStart : Bool
    }


init : () -> ( Model, Cmd Msg )
init _ =
    ( Model Time.utc (Time.millisToPosix 0) 0 30 False
    , Task.perform AdjustTimeZone Time.here
    )



-- UPDATE


type Msg
    = Tick Time.Posix
    | AdjustTimeZone Time.Zone
    | DoTimer
    | ChangePlayer


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Tick newTime ->
            let
                c =
                    if model.isStart then
                        model.counter + 1

                    else
                        model.counter
            in
            ( { model | counter = c }
            , Cmd.none
            )

        AdjustTimeZone newZone ->
            ( { model | zone = newZone }
            , Cmd.none
            )

        DoTimer ->
            let
                r =
                    if model.isStart then
                        False

                    else
                        True
            in
            ( { model | isStart = r }, Cmd.none )

        ChangePlayer ->
            ( { model | counter = 0 }
            , Cmd.none
            )



-- SUBSCRIPTIONS


subscriptions : Model -> Sub Msg
subscriptions model =
    Time.every 1000 Tick



-- VIEW


view : Model -> Html Msg
view model =
    let
        c =
            String.fromInt model.counter

        l =
            String.fromInt model.limit

        bt =
            if model.isStart then
                "Stop!"

            else
                "Start"

        btClass =
            if model.isStart then
                "bt stop-bt"

            else
                "bt"
    in
    div [ class "grid-container" ]
        [ div [ class "start" ]
            [ div
                [ class "area-overlap start-bt" ]
                [ input [ type_ "button", value bt, onClick DoTimer, class btClass ] []
                ]
            ]
        , div
            [ class "counter" ]
            [ h1 [] [ text c ]
            ]
        , div
            [ class "change" ]
            [ input [ type_ "button", value "Change", onClick ChangePlayer, class "bt change-bt" ] []
            ]
        ]


main =
    Browser.element
        { init = init
        , update = update
        , subscriptions = subscriptions
        , view = view
        }

```

これをわざわざreact-reduxでやろうと思うと大変ですよね。
store作って、reducer, action分けて。。などなど

勉強会LT聞いてた感じだと、実際にアプリケーション作られた方でも
`Main.elm`につらつら書いたということで
今回は僕もその方針に従ってみました。

### コード大枠の説明

elmでは、アプリケーションで扱う状態を`Model`で定義します。
アプリケーション内部で定義されたイベント(`Msg`)は、`update`に入り
このときに`Model`の状態を書き換えます。
`Model` は `view` を使って描画されます。

redux の仕組みを理解されてるかたは、あー同じ仕組みじゃんと感じるかと思います
そもそもreduxがこのelm architectureを参考に作られたようなのでそれもそのはずですね。

### ハマったこと

#### if の使い方

returnが不要なので以下のように書く

```elm
bt =
        if model.isStart then
            "Stop!"

        else
            "Start"

```

#### Modelの初期化

Modelのfieldを宣言しなくてもかけるので
fieldの順に値渡せばよい

```elm
type alias Model =
    { zone : Time.Zone
    , time : Time.Posix
    , counter : Int
    , limit : Int
    , isStart : Bool
    }

init : () -> ( Model, Cmd Msg )
init _ =
    ( Model Time.utc (Time.millisToPosix 0) 0 30 False
    , Task.perform AdjustTimeZone Time.here
    )
```

### よいところ


- ちょっとしたもの作りたいときにすぐ始められる
    - データフロー定義されていて、型もあるので申し分ない
- 公式のlinterがあるのでcode formatもしっかりできる
- コンパイラが賢い
    - 公式のここ見ろとかリンクまで出してくれる、すごい


### 気になるところ

- 副作用の扱い
- spaとかにしたらコードがごちゃついてくるのか
    - oss読んでみる