Help us understand the problem. What is going on with this article?

Elm 0.18 入門 (1)カウントアプリを作ってみる

More than 3 years have passed since last update.

関数型フロントエンド言語 Elm 0.18 のチュートリアルです。

  • ブラウザだけで開発を始める
  • シンプルなモデルの定義
  • モデルの状態を画面に表示する
  • アクションを定義してモデルの状態を更新する

WebでElmをはじめる

Ellie というオンラインエディタでElmを始めてみましょう。こちらはElmコードとそれを埋め込むためのHTMLコードをそれぞれ編集したり、Elmのパッケージを追加して開発することができます。

Ellie画面

最初に書いてあるコードはElm側はテキストを表示するだけです。

module Main exposing (..)

import Html exposing (Html, text)


main : Html a
main =
    text "Hello, World!"

一方HTML側では、Elmの呼び出しとCSSの記述があります。

<html>
<head>
  <style>
    html {
      background: #F7F7F7;
      color: red;
    }
  </style>
</head>
<body>
  <script>
    var app = Elm.Main.fullscreen()
  </script>
</body>
</html>

文字が全部真っ赤だと辛いので、 color: red; だけ消しておきましょう。

 html {
   background: #F7F7F7;
-  color: red;
+  /*color: red;*/
 }

コンパイルを実行すると反映されます。
以降のコードではHTML側は編集しません。

モデルを作って画面に表示する

では、アプリケーションの作成をしてみましょう。
Elmはアプリケーションの状態である「データモデル」を中心に、基本的に2つの処理を書きます。

  • データモデルをHTML表示にする(View)
  • データモデルをアクションに合わせて更新する(Update)

いきなり動きのあるアプリケーションを作る前に、まずはModelとViewから始めてみましょう。

アプリケーション全体のモデルとなる Model 型を作ります。 Int そのものでも良いのですが、名前をつけてカウントを管理してみましょう。初期値は 0 としています。

type alias Model =
    { count : Int
    }

initModel : Model
initModel =
    { count = 0
    }

type alias Model が型の定義部分で、 initModel : Model は Model型の初期値を返す関数です。後ほど使います。

次に、先程は作らないと言っていた update を作ります。ただし、定義するアクションは NoOp (No Operation, 何もしない)のみです。

type Msg
    = NoOp

update : Msg -> Model -> Model
update msg model =
    case msg of
        NoOp ->
            model

内容は後述しますが、ここでは何もしないことだけを定義しています。

続いて view の定義です。ElmではHTMLの記述も行います。

view : Model -> Html Msg
view model =
    p []
        [ text "count: "
        , text (toString model.count)
        ]

view : Model -> Html Msg はあるモデルの状態を受け取ってHTMLを返す関数で、
p はHTMLタグ <p> に対応しており、属性のリスト [] とタグの内容 [ text ... ] の2つを受け取ってHtml を返す関数です。

ただ、この状態ではコンパイルエラーが発生します。理由は、 p がインポートされていないためです。 import を修正しましょう。

-import Html exposing (Html, text)
+import Html exposing (Html, text, p)

最後に、アプリケーションとして機能するように関数を組み立てます。既に main 関数があると思いますが、一旦削除して次のように変更して下さい。

main : Program Never Model Msg
main =
    Html.beginnerProgram
        { model = initModel
        , update = update
        , view = view
        }

コンパイルしてプレビューに「count: 0」と表示されることを確認しましょう。
ここまでの全体像は次のようになります。

https://ellie-app.com/4494JhJ7PcJa1/3

module Main exposing (..)

import Html exposing (Html, text, p)


main : Program Never Model Msg
main =
    Html.beginnerProgram
        { model = initModel
        , update = update
        , view = view
        }


type alias Model =
    { count : Int
    }


initModel : Model
initModel =
    { count = 0
    }


type Msg
    = NoOp


update : Msg -> Model -> Model
update msg model =
    case msg of
        NoOp ->
            model


view : Model -> Html Msg
view model =
    p []
        [ text "count: "
        , text (toString model.count)
        ]

初期値を変更してみたりviewの内容をいじって再コンパイルしてみてください。

カウントアップを実装する

ボタンを押すとカウントアップする機能を実装してみましょう。

まず、アクションの種類を追加します。

type Msg
    = NoOp
    | Increase

type Msg = NoOp | Increase という表記は、「 Msg 型の値は NoOp または Increase のどちらかである」ということを定義しています。 NoOp などの具体的な定義がない、と思う方もいるかもしれませんが、 NoOp はそれ自体がコンストラクタの役割も持っており、 Msg 型の値そのものです。
バックグラウンドを詳しく知りたい方は代数的データ型などを参照して下さい。

続いて、アクションへの具体的な対応(モデルの更新)を実装します。

update : Msg -> Model -> Model
update msg model =
    case msg of
        NoOp ->
            model

        Increase ->
            { model | count = model.count + 1 }

case msg of は「パターンマッチ」です。ここでは Msg の具体的な内容に応じて場合分けされていて、 NoOp の場合は現在のmodelをそのまま返し、 Increase の場合はモデルの更新を行っています。
{ model | ... } はモデルの一部分だけを更新した新しいモデルを作る構文です。ここでは count を現在のモデルのカウント model.count+ 1 した新しいモデルを返しています。

この状態でコンパイルすることができますが、肝心の Increase アクションを起こす方法を定義していません。
view にボタンを作ってカウントアップできるようにしてみましょう。

view : Model -> Html Msg
view model =
    div []
        [ counter model
        , increaseButton
        ]


counter : Model -> Html Msg
counter model =
    p []
        [ text "count: "
        , text (toString model.count)
        ]


increaseButton : Html Msg
increaseButton =
    div []
        [ button [ onClick Increase ]
            [ text "+1" ]
        ]

先程までの p をカウンター表示用の関数 counter から返すようにして、 increaseButton を追加しました。 increaseButton はモデルの状態によって変更する部分はないので Model を引数に含めていません。

最後に、div, button タグやイベントを実行するための onClick をインポートしましょう。

-import Html exposing (Html, text, p)
+import Html exposing (Html, text, p, div, button)
+import Html.Events exposing (onClick)

それではコンパイルして確認してみましょう。上手くカウントアップできているでしょうか?

ここまでのコードは以下で確認できます。

https://ellie-app.com/4494JhJ7PcJa1/4

状態のリセットとデバッグ

このアプリケーションにはブラウザなどに状態を記録してはいないので、アプリケーションをリロードするとカウントが初期値に戻ります。プレビュー画面右下の「RELOAD」をクリックして確認してみましょう。

また、Elm 0.18では標準でデバッグモードを搭載しており、Eliieでも確認することができます。プレビュー画面左下の「DEBUG」をクリックしてみましょう。

Debugger

アクションの履歴と、そのアクションが実行されたあとのモデルの状態が記録されており、クリックするとそのモデルの状態を画面で再現してくれます。
現在のモデルの状態に戻るには「Resume」をクリックします。

宿題

  • カウントダウン
  • "+5" ボタン

などを実装してみましょう。カウントダウンでマイナス値にならないように制御するにはどうすればよいでしょうか?

まとめ

Elm Architectureの一番簡単なモードである beginnerProgram で小さなアプリケーションを作ってみました。いかがだったでしょうか?
モデルの定義とモデルを更新するためのアクション、そしてモデルの表示方法にきちんと分離されていてきれいな仕組みだと思います。

気になった方はぜひ自分なりのサンプルアプリケーションを作ってみて下さい。

次回: Elm 0.18 入門 (2)パラメータ付きのアクションと入力フォーム

mather314
元数学専攻のエンジニア。Web系のアプリケーション開発がメインだが、HTMLやCSSよりはバックエンドロジックの分析などが得意。Haskell, Scala, Elmなどを嗜む程度に。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away