LoginSignup
12

More than 5 years have passed since last update.

Elm Architecture + Effects (パート1)

Last updated at Posted at 2016-06-24

おさらい

Elmアーキテクチャについては、以下の記事で簡単に紹介しました。

ViewからUpdateが発行されたらModelが変更され、Viewが再レンダリングされるという世界ですね。

今回は、ElmアーキテクチャにEffectを盛り込んでいきます。

この記事は、The Elm Architecture + Effects に沿って進めていきます。

Effectsとは

  • Commands
  • Subscriptions

Effectsとは上記の機能です。これらの機能を用いることで、Elmのコンポーネントが外部の世界とつながりをもつことができます。
SubscriptionsはElm0.17の目玉機能でもありますね。

関数型リアクティブプログラミングにさよならを

Commandsとは

何かしらのEffectを要求するものらしいです。
乱数を求めたり、HTTPリクエストを発行するのがCommandの役割だとか。実行結果は外部要因によって変化しえます。

Subscriptionsとは

Subscriptionsとは、Geolocationの変化や、WebSocket経由でのメッセージ受信のようなイベントを待ち、それらが起こると更新処理が走るような仕組みです。

アーキテクチャのスケルトンを拡張

Effectsを盛り込んだアーキテクチャスケルトン(骨組み)は、以下のようになります。

-- Model
type alias Model =
  { ...

  }

-- Update
type Msg = Submit | ...

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  ...

-- View

view : Model -> Html Msg
view model =
  ...


-- Subscriptions
subscriptions : Model -> Sub Msg
subscriptions model =
  ...

-- Init

init : (Model, Cmd Msg)
init =
  ...

Subscriptionsやinitが追加されたり、updateの結果にCmdというものが増えたのがわかります。

以下、詳しく見ていきます。

update関数

update関数は、新しいModelだけでなく、実行したいコマンドも返すようになりました。これらのコマンドはすべて、Msgを生成しますが、Msgはupdate関数へと送られます。(Msgはアクションを識別する名前のようなものでした。)

subscriptions関数

subscriptions関数では、現在のModelの変更を監視するためのイベントソースを宣言します。
Html MsgやCmd Msgのように、subscriptionsもupdate関数に渡すためのMsgを生成します。

init

今回は、initは単なる初期Modelです。(Reduxで言うところのinitialState)
update関数のように、ModelとCommandsを提供します。ここでは、初期化のために値を渡したり、最初のHTTPリクエストを発行するような、初期化に必要なすべてのことを行います。

いまはまだこれらのことが分かっていなくても大丈夫です。実際の動きを見てみましょう。

Rolls Dice

まずは、1~6の数値をランダムに生成するような”rolls dise”というアプリケーションを作成します。

ここでは、最小構成をつくるフェーズと、詳細を埋めていくフェーズの2つに分けて進めていきます。

フェーズ1 最小構成

まずはModelです。ModelはシンプルにInt型のデータを1つ持ちます。

-- Model
type alias Model =
  { dieFave : Int
  }

次にViewを書きます。

-- View
view : Model -> Html Msg
view model =
  div []
    [ h1 [] [ text (toString model.dieFace)]
    , button [ onClick Roll ] [ text "Roll" ]
    ]

次にUpdateを埋めていきます。

-- Update
type Msg = Roll

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    Roll ->
      (model, Cmd.none)

Elmアーキテクチャを学んだ時と異なり、update関数はModelだけでなくCmdも返しています。
ただし、今のところは、Cmd.none(なにもしない)をセットしておきます。

この部分が「ランダムな数値を取得する」に置き換えられるわけですね。

最後に、initをつくります。

init : (Model, Cmd Msg)
init =
  (Model 1, Cmd.none)

ちなみに、subscriptionsはまだ使わないので、以下のようにしておきます。

-- Subscriptions
subscriptions : Model -> Sub Msg
subscriptions model =
  Sub.none

なぜ使わないのに、subscrptionsを定義するかというと、いわばnot nullのようになっているからです。

最後に、ファイルの先頭にimportやスタート記述を書きます。
現時点でのファイルは、以下のようになっているはずです。

import Html exposing (..)
import Html.App as App
import Html.Events exposing (..)
import Random

main =
  App.program
    { init = init
    , view = view
    , update = update
    , subscriptions = subscriptions -- ←App.programはsubscriptionを要求する。
    }

-- Model
type alias Model =
  { dieFace : Int
  }

-- Update
type Msg = Roll

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    Roll ->
      (model, Cmd.none)

-- View
view : Model -> Html Msg
view model =
  div []
    [ h1 [] [ text (toString model.dieFace)]
    , button [ onClick Roll ] [ text "Roll" ]
    ]


-- Subscriptions
subscriptions : Model -> Sub Msg
subscriptions model =
  Sub.none

-- Init

init : (Model, Cmd Msg)
init =
  (Model 1, Cmd.none)

これでフェーズ1は終了です。
コンパイルが通るので、ブラウザで確認ができます。やりましたね!

フェーズ2 完成

乱数発生周りが大きく欠けている状態なので、そこを埋めます。

まずはupdateを追加します。

-- Update
type Msg
  = Roll
  | NewFace Int -- New!

NewFace関数が呼ばれると、Elmが乱数を渡してくれます。

関数部分も適用したupdateがこちら。

-- 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 ->
      (Model newFace, Cmd.none)

いくつかの変更が行われていますね。

  1. NewFaceメッセージ(Msg)を受け取ったパターンを追加しました。Modelを渡して、Commandは何も実行しません。
  2. Rollに変更があります。Randomライブラリの持つ関数を2つ使っていますね。

大切なのは、Random.generateです。

Random.generate : (a -> msg) -> Random.Generator a -> Cmd msg

Random.generate関数は2つの引数を取ります。1つ目が乱数をタグ付けするための関数です。今回は、Intを取り、Msgを返すNewFace関数を渡しました。
そして、2つ目の引数は”generator”です。

generatorはIntだけでなく、FloatやStringの値も生成できるようです。

Random.int : Int -> Int -> Random.Generator Int

これで、要求通りに機能するアプリケーションができました。

ここまでの学びは以下のとおりです。

  • プログラムは少しずつ書きすすめること。シンプルなスケルトンからはじめて、徐々にタフな機能をつけていく。
  • update関数は、いまや新しいModelとCommandを生成します。
  • すぐにランダム値を取得することはできません。Commandを生成して、Elmに生成させるのです。

完成したコードは以下のとおりです。

import Html exposing (..)
import Html.App as Html
import Html.Events exposing (..)
import Random

main =
  Html.program
    { init = init
    , view = view
    , update = update
    , subscriptions = subscriptions
    }

-- Model
type alias Model =
  { dieFace : Int
  }

-- 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)

-- View
view : Model -> Html Msg
view model =
  div []
    [ h1 [] [ text (toString model.dieFace)]
    , button [ onClick Roll ] [ text "Roll" ]
    ]


-- Subscriptions
subscriptions : Model -> Sub Msg
subscriptions model =
  Sub.none

-- Init

init : (Model, Cmd Msg)
init =
  (Model 1, Cmd.none)

長くなってしまったので、ここでいったん切ります。
次回は、HTTP、時間ごとの処理、アニメーション、WebSocketsについて学んでいきます。

最後にもう1度、本家のリンクを貼っておきます。

何か間違いなどがありましたら、コメントでお知らせください。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12