Elmは純粋関数型言語ですから、関数としてランダムな数字を扱うことができません。JavaScriptのMath.random()のような関数はございません。それは純粋関数の定義が、同じ引数を与えられれば同じ値を返す、という条件を要求するからです。Math.random()はむしろコールされる度に違う値を返すことを期待されている関数ですから、純粋関数とは真逆の存在ですね。
ElmにはHaskellのようなIOモナドがないので、この汚れ仕事をElm Runtimeに下請けします。Model-View-Updateループは、ユーザINPUTのイベントが発生したら、updateを発生させ、modelを更新し、Viewに反映させるという事が繰り返されるものでした。この場合も、イベントの管理やViewのHtmlをDOMで表示するのはやはりElm Runtimeの役割でした。ザックリ言って、Elmでは純粋でない処理はElm Runtimeに任せるのが定石のようです。
https://qiita.com/sand/items/d9830b4226710a969488
ランダムな数字を発生させるのはModel-View-Updateの枠組みでは処理できません。ElmはModel-View-Updateのアーキテクチャを拡張し、Cmd(Commands)という概念を導入した新アーキテクチャをつくりました。これで乱数を扱います。ちなみにSub(Subscriptions )も導入されましたがこれについては次回以降にしたいと思います。
https://guide.elm-lang.org/effects/
つまり今までは viewでHtmlをElm Runtimeに送り付けていただけなのが、新アーキテクチャではCmdとSubというインターフェースが増えたと言えます。
Elm Runtime | -> update -> Html -> | Elm Runtime
| -> Cmd -> |
| -> Sub -> |
それでは公式サイトに従って、Elmの乱数の扱いについて見ていきたいと思います。
https://guide.elm-lang.org/effects/random.html
module MyRandom exposing (..)
import Html exposing (..)
import Html.Events exposing (..)
import Random
main =
Html.program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
-- MODEL
type alias Model =
{ dieFace : Int
}
init : (Model, Cmd Msg)
init =
(Model 1, Cmd.none)
-- UPDATE
type Msg
= Roll
| NewFace Int
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
Roll ->
(model, Random.generate NewFace (Random.int 1 6))
NewFace newFace ->
(Model newFace, Cmd.none)
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
-- VIEW
view : Model -> Html Msg
view model =
div []
[ h1 [] [ text (toString model.dieFace) ]
, button [ onClick Roll ] [ text "Roll" ]
]
Cmdを取り入れた新アーキテクチャではHtml.beginnerProgram の代わりに Html.programを使います。
main = Html.beginnerProgram { model = model, view = view, update = update }
main = Html.program { init = init, view = view, update = update, subscriptions = subscriptions}
この新アーキテクチャの効力はupdate関数に現れます。以下の型宣言に注意してください。
update : Msg -> Model -> (Model, Cmd Msg)
update関数の返り値として、単にModelだけでなく、Cmd Msgも生み出されます。この点に注意しておいてください。以下に詳しい説明を続けます。
このプログラムは画面のボタンをクリックするとランダムな数字を発生し、それを表示するといううものです。このElmプログラムの役割は、ソースの通りですけど、実際にHtmlをDOM上に表示したり、ランダムな数字を発生したりするのはElm Runtimeの仕事になります。
前述の通り、以下のupdateコードが肝ですので説明していきたいと思います。
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
Roll ->
(model, Random.generate NewFace (Random.int 1 6))
NewFace newFace ->
(Model newFace, Cmd.none)
updateは返り値として、Modelだけでなく Cmd Msgも返します。Cmdは型変数(型パラメータ)をとる型で、Cmd Msgで一つの型を表します。Cmdは、純粋なElmプログラムが、汚い仕事は自分ではやれないから、代わりにElm Runtimeでやってくださいという指令のコマンドを表しています。純粋な関数と区別するためにコマンドという言葉が新たに採用されたのでしょう。Cmdの処理が終了すると、生成された値を持つMsgがupdateに送られます。つまりupdateが2回続けて呼ばれることになります。乱数を発生させろというコマンドを作るupdateと、そのコマンドが終了したら得られた値を純粋なElm関数のもとに返すためのupdateです。この辺が最初はわかりにくいですね。
ここでは以下に注目します。マウスでボタンをクリックすると呼び出されるupdateの処理です。ちなみにこの時点でmodelの変更はありません。
Roll ->(model, Random.generate NewFace (Random.int 1 6))
まずRandom.generateは関数で Cmd Msgの値を返します。引数を2つ取って、この一行でElm Runtimeにランダムな数字を発生してくれるようにお願いのコマンドを発行します。Elm Runtimeはコマンドの処理が終わったらその結果をupdate関数を呼ぶことで返してくれます。この時のMsgはNewFace となります。
繰り返しになりますが、Elm Runtimeは上のCmdを処理すると、updateを呼び以下の処理を行います。newfaceがランダムな数字で、Model newFaceの記述は、その値で構築されたModel型の値です。この時点でmodelが更新されました。Cmd.noneはもはやコマンドが無いことを意味します。しかし処理によってはコマンドを連続して発行できる仕組みですね。更新されたmodelは、Viewに反映され、この乱数が画面に表示されます。
NewFace newFace ->(Model newFace, Cmd.none)
今回はsubscriptionsについては触れません。今回はSub.noneだけを指定しているだけです。いつか触れたいと思っています。