Elm開発環境について
Elm Architecture の Model-View-Updateについて
(2019/01/30 修正) 0.19対応 toString を String.fromInt で置き換えました。
Elmは純粋な関数型言語であり、Webアプリ専用の言語でもあります。両者は相反するように思えますが、HaskellでもYesodを使ったWebアプリを作成できますので...。要するにHaskellは純粋でないダーティな部分はIOモナドで隔離します。同じようにElmではElm Runtimeにダーティな部分を任せて、自身の純粋さを保つようにしているようです。
ElmはWebアプリのArchitecture をModel-View-Updateという形でパターン化しています。前回取り上げたソースコードで説明していきます。公式サイトにあるボタン表示のプログラムを微修正したものです。
https://guide.elm-lang.org/install.html
1.View Only
その前に以下の簡略化されたソースコードを見てください。これは35という数字をブラウザに表示してくれるプログラムです。
module MyButtons exposing (..)
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)
main = view "no model"
view model =
div [] [ text (String.fromInt 35) ]
MyButtons.elmはコンパイルされてelm.jsになります。以下のようにindex.htmlでelm.jsは読み込まれます。そしてElm.MyButtons.embed()でMyButtons moduleのmain関数がこのアプリのエントリーポイントとして使われるようになります。つまり関数 view "no model" が実行されます。
<div id="elm-area"></div>
<script src="elm.js"></script>
<script>
Elm.MyButtons.embed(document.getElementById("elm-area"));
</script>
view関数は引数としてmodelを取りますが、右辺では使われていないのでダミーで十分です。結果的にHTML(DOM)を生成します。div [] [ text (String.fromInt 35) ] はdivタグを表します。最初のリスト[]は属性のリストで、2番目のリスト[ text (String.fromInt 35) ] は子要素のリストです。つまり <div>35</div>となり、index.htmlの中で以下のように展開されます。
<div id="elm-area"><div>35</div></div>
ブラウザにはこの35という数字が表示されるだけです
2.Model-View-Update
今度はボタンを設置して、表示する数字を増減させることができるようにします。前回と同じソースコードを以下に示します。
module MyButtons exposing (..)
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)
main =
Html.beginnerProgram { model = model, view = view, update = update }
-- MODEL
type alias Model = Int
model : Model
model =
0
-- UPDATE
type Msg = Increment | Decrement
update : Msg -> Model -> Model
update msg model =
case msg of
Increment ->
model + 1
Decrement ->
model - 1
-- VIEW
view : Model -> Html Msg
view model =
div []
[ button [ onClick Decrement ] [ text "-" ]
, div [] [ text (String.fromInt model) ]
, button [ onClick Increment ] [ text "+" ]
]
このプログラムは、内部的にカウンターを持っていて、インクリメントボタンとディクリメントボタンの2ボタンを表示し、間に現在値を表示しています。インクリメントボタンをクリックするとカウントアップした値が表示され、ディクリメントでカウントダウンした値が表示されます。特にブラウザをリフレッシュすることなしに、ブラウザ上の値は動的に変わっていきます。
このモデルは以下のような流れになります。
ユーザINPUTが発生 → modelを更新 → viewに更新を反映 → ユーザINPUTが発生 → ....
これはElm RuntimeがユーザINPUTイベントの管理やHTML表示を司っているので、以下のように書くのがもっと正確です。
(MSG) (Html)
Elm Runtime → update → model → view → Elm Runtime
上のようなモデルは以下のmain関数で定義されています。繰り返しになりますが、内部カウンター(model)をマウスクリックでupdateし、その結果をviewに反映させます。ユーザのINPUTをハンドリングし、modelをupdateし、そのupdateをviewに反映させます。これを繰り返すのがELM Architecture の Model-View-Update Loopです。
main =
Html.beginnerProgram { model = model, view = view, update = update }
Html.beginnerProgram()はModel-View-Update Loopを実現化します。マウスでボタンが押されると(ユーザのINPUTがあると)、Elm RuntimeはMSG付きのイベントを発生させupdate関数を呼びます。update関数はMSGに従ってmodelを更新します。更新されたmodelでviewが呼ばれ、新しいHtmlが生成され、Elm Runtimeに渡され実際にブラウザで表示されます。つまりイベントとかDOM表示とかは全てElm Runtimeが下請けします。Virtual DOMが使われていますが、Elm Runtimeは高速に描画してくれるらしいです。
ちなみにElm Runtimeはコンパイラが生成してくれるJavaScriptコードです。ソースコードに従ってイベントハンドラーを設定してくれたり、HTTP RequestやDON updateの効率的なスケジューリングを行ってくれたりします。
それと指摘しておきたいのがmodelですが、これはElmのデータですので直接書き換えることはできません。一部分を変更しながらコピーすることで、書き換えの代替としています。Elmは純粋ですから。
3.Type
Haskellは型が強い言語です。コンパイラの型チェックがとても厳しくて、逆に型チェックが通れば、大方デバッグ無しで動くともいわれています。おなじようにElmにも型があります。上のプログラムで少し説明を加えます。
以下はModelという方を定義しています。Int型の別名として定義されているのでaliasが付いています。
type alias Model = Int
以下はMsg型を定義しています。Msg型はIncrement または Decrement のことを指します。
type Msg = Increment | Decrement
以下は関数updateの型です。Msg型とModel型の引数を取り、Model型の値を返します。
update : Msg -> Model -> Model
以下は関数viewの型です。Model型の引数を取り、Html Msg型の値を返します。Html Msg型にはtype variableのMsgが含まれています。Htmlは引数を必要とする方で、Html Msgではじめて一つの型になります。一般的にはHtml a で a
がtype variableで任意の型が入ります。この場合はMsgがtype variableの値です。
view : Model -> Html Msg
今回は以上ですが、Elmの話はまだ続きます