ElmのStartApp.Simpleの仕組み

追記
この記事は古いです。過去のものです。忘れましょう

#対象読者

Elmを勉強しようと思って[公式のドキュメント](http://elm-lang.org/docs)読んでで[elm-architecture-tutorial](https://github.com/evancz/elm-architecture-tutorial/blob/master/examples/5/RandomGif.elm)を見てどういう仕組みだよこれってなった人（つまり私）
コードとかは上記チュートリアルとライブラリからの引用。内容としてはライブラリのコードを追っかけて理解できたことをまとめている。javascriptまでは踏み込んでない

こんなバージョン。0.15.1

```json:elm-package.json
"dependencies": {
    "elm-lang/core": "2.1.0 <= v < 3.0.0",
    "evancz/start-app": "2.0.1 <= v < 3.0.0"
},
"elm-version": "0.15.1 <= v < 0.16.0"
```

#全体を見る

```elm:Main.elm
import Html exposing (div, button, text)
import Html.Events exposing (onClick)
import StartApp.Simple as StartApp

main =
  StartApp.start { model = model, view = view, update = update }

model = 0

view address model =
  div []
    [ button [ onClick address Decrement ] [ text "-" ]
    , div [] [ text (toString model) ]
    , button [ onClick address Increment ] [ text "+" ]
    ]

type Action = Increment | Decrement

update action model =
  case action of
    Increment -> model + 1
    Decrement -> model - 1
```

modelとviewとupdateを用意してStartApp.Simple.startにかませればあとはよしなにやってくれるらしい……

#Addressを理解する
上記view関数の引数にaddressが登場してます。チュートリアルをみるとviewの型は`view : Signal.Address Action -> Model -> Html`で引数`address`の型は`Signal.Address Action`です。

この`Address`型はSignal.elmで定義されています

```elm:Signal.elm
type alias Mailbox a =
    { address : Address a
    , signal : Signal a
    }
type Address a =
    Address (a -> Task () ())
```

`Address`型は`Mailbox`型とセットです。`Mailbox`は関数`Signal.mailbox : a -> Mailbox a`で作ります。中には`address`と`signal`が入っています。
この中の`address`に向かってMessage（a型の値）を送ると`signal`が更新されます。

`view`関数で使われている`address`の正体はこの型です。型は`Address Action`なので`StartApp.Simple.start`の中では、`Mailbox Action`型の値があってそこから`Signal Action`を取り出して使っているんでしょう。（実際は少し違います）

#`StartApp.Simple`の実装
短くてsimpleです

```elm:Simple.elm
type alias Config model action =
    { model : model
    , view : Address action -> model -> Html
    , update : action -> model -> model
    }
start : Config model action -> Signal Html
start config =
  let
    actions =
      Signal.mailbox Nothing

    address =
      Signal.forwardTo actions.address Just

    model =
      Signal.foldp
        (\(Just action) model -> config.update action model)
        config.model
        actions.signal
  in
    Signal.map (config.view address) model
```

`Config`型が引数として渡している`{ model = model, view = view, update = update }`の型です。`start`関数は`Action`の`mailbox`を作ってそこから`Action`を取り出して`Signal Model`を作り`view`関数にmapしてます。

##forwardTo
そしてチュートリアルの２にもある`Signal.forwardTo`関数を使ってます。`mailbox`の型は正確には`Mailbox Maybe a`です。`address`には`Action`を送るのでそのまま`Action`が送れる型に`address`を変えておく必要があります。

```elm:
forwardTo : Address b -> (a -> b) -> Address a
forwardTo (Address send) f =
    Address (\x -> send (f x))
```
(a -> b)を使って渡されたaの型を変換しています。`Signal.forwardTo actions.address Just`では`just`が使われています。
どうやら`Just Decrement`で`Maybe Action`型となるので`Just`は`a -> Maybe a`型になるようです。

`forwardTo`でできた`address`に`Action`を送ることで`mailbox`が更新されるようになりました。
実際に`address`に`Action`を送っているのは`onClick`関数などです。

#最後に
こうしてみると`StartApp.Simple`は`MailBox`を中心とした薄めのラッパーという感じがします。ちょっとした仕組みでフレームワークみたいなものを実現していて驚きました。
Elmは日本語情報少ないので困りましたが結局コード読んでたら何とかわかりました。
せっかくなのでまとめてみました。学習の手助けになれば幸いです。