heimdalの技術ノート

IT全般、Linux、Windows、プログラミング開発など、何でもござれ

初めてelm-lang(ver0.19)を触る人を対象に分かりやすく解説してみた。 sandbox編

はじめに

Elmは面白い。関数型の独特な雰囲気を持ち、変数不要な不思議なコーディング感覚がまた楽しい。
そんなElmが先日0.19へバージョンアップしました。

以前、Elmを記事にしたが、0.19により、破壊的な変更があり、0.18のソースでは動かなくなりました。
heimdal.hatenablog.com

そこで、Elm0.19用に再入門してみました。 例によってelectron+elmです。

準備

ここのサイトからソースコードをダウンロードしてください。

github.com

環境

mac
electron
node.js
elm

使い方

ソースコードを使うには以下のコマンド実施すれば、動作を確認できます。
1. npm -g i electron elm
2. elm make src/sample1-sandbox/Main.elm --output=dist/app.js
3. electron .

Elmアーキテクチャ

ElmにはElmアーキテクチャなるものがあり、model, update, viewで構成されています。
そして、この構成が0.19ではsandboxelementdocumentapplicationの4種類の用途別に用意されています。 出来ることも少しずつ違います。
以下、ざっくりと解説します。

sandbox

model、update、viewの基本構成のみを使うことが出来ます。副作用になる外部通信はない構成で、シンプル構成になっています。
今回は余計な宣言や記載は混乱を招くので、一番シンプルなsandboxを選択しました。

element

sandboxの機能に、httpなどの外部通信できるように機能を加えたものです。

document

elementの機能にviewの機能を拡張し、titleとbodyを柔軟に変更できるようになります。

application

elementにSPA特化の機能を拡張し、urlによるイベントを柔軟に行えるようになります。

解説

elm.jsonが出来たりと、様々な変更がありますが、それらは今回は割愛してMain.elmをメインに解説します。

必要なものをインポート

module Main exposing (Model, Msg(..), init, main, update, view)

import Browser
import Html exposing (Html, div, input, text)
import Html.Attributes exposing (placeholder, value)
import Html.Events exposing (onInput)

一行目は、このファイルをモジュールとして使用する際にどの関数を使うかを記載するための宣言です。 インポートされた場合、括弧内の関数が使え、括弧内に無い関数はこのファイルはインポートされる事はないため、書く必要はないですが、念のため書きます。

それ以降は、今回必要な関数をインポートします。 import Html exposing (..)とすれば、Htmlで使える全ての関数を使用することが出来ますが、
import Html exposing (Html, div, input, text)のように今回はあえて絞るように記載してます。

main構成

main : Program () Model Msg
main =
    Browser.sandbox
        { init = init
        , update = update
        , view = view
        }

最初に読み込む箇所を作成します。
Elmには関数毎に型を明記することが出来ます。
main : Program () Model Msg
この型の意味はFlagを持たず、Model型のモデルを持ち、Msg型のメッセージを実行するプログラムを指しています。

Haskell同様、型推論があるので、型を書く必要は無いですが、原則書くことをオススメします。 理由としては、Haskell同様、自身で型を書くことで、その後の処理で型違いとコンパイルエラーが出た場合のエラー内容がより親切に分かりやすくなるからです。

自身がtype宣言による分かりやすい型表記のエラー文言で返ってくるか、または型推論による複雑なエラー文言か、の差は大きいです。

またそのエラー文言から、自身が書いた型が想定通りの型なのか、その後の処理の型の方が正しかったのか、など、仕様の変更するかの判断材料としても非常に助かります。よって型は自分で書くようにしましょう。どういう型にすれば良いかわからない場合は、仕様が決まっていないか、またはまだ理解出来ていないという事になります。

※補足として()は何もないという意味を指しています。ModelやMsgはどこから出てきたかはこの後のソースを見ればtype宣言しているのが分かる。その型を参照しています。

モデル

type alias Model =
    { content : String
    }

Elmの各関数はJavaScriptの「let hoge = 1;」のような状態を保持しない仕組みです。
そのため、モデルという所を作成し、そこに状態を保持します。
そのおかげで、各関数は純粋な関数として作成できます。

モデルの型を宣言する。例ではModelにしているが、Hogeでも何でもOKです。
この型を基準として、上のmain関数の型としての役割もあります。
補足:type aliasは特定のオブジェクトのような構造を任意の名前で型宣言したという意味です。
複雑な構造(レコード)に名前を付けることで型の表記を簡単にできます。
でないと、すぐ下の
init : Model

init : {content : String}
と書かなければいけなくなり、名前を付けないと、なぜその型なのか意味もわからなくなってしまいます。

初期化

init : Model
init =
    { content = ""
    }

初期化する処理を実行します。ここでは空文字に初期化しています。
モデルとして管理するために、上のmain関数のinitにセットしています。
その際に、initの型とmainの型がModelで統一していなければ、コンパイルで怒られます。

余談...
普通はやらないが、補足として、Modelでなく、HogeHogeという型でもモデルの型の中身が同じ({ content : String})であれば、一応コンパイルは通ります.

更新

type Msg

type Msg
    = InputContent String

モデルを更新する処理は全てupdateに書きます。
Msg(メッセージ)という言葉は馴染みがないが、最初は「Msg = アクション」という解釈の方が分かりやすいかもしれない。
が、実際理解が深くなっていくと、アクションというより、「何かしら処理したいための識別タグ」のようなものの方がしっくりくると思う。
つまり、InputContent自体は処理がなく、ただの識別に使う名前と思えば良いと思う。
以上を理解したら、次の文章の意味もスッと入ってくるはずだ。
「どこからかInputContentというメッセージが送られてきた場合は、必ずStringの文字列がくっついて送られてくる。
そのメッセージを受け取ったら、Updateにあるケース文でどれかを特定し、その処理をする」

update

update : Msg -> Model -> Model
update msg model =
    case msg of
        InputContent newContent ->
            { model | content = newContent }

モデルを更新する処理を書きます。
処理するメッセージとモデルを引数にして、何かしらの処理をし、戻り値としてモデルを返し、反映するという仕組みになります。

ここにあるケース文はメッセージを判別するためのものです。
Msgと型宣言しているので、ここに入ってくるmsgは必ずtype Msgで宣言したものしか入ってこれないようになっている。
補足:Msgは型です。msgは引数の名前です。全く違うものなので、
「Msgとmsgがあって何で大文字小文字で使い分けているの!?」と変な混乱しないように注意すること。

modelのレコードを更新するには以下の書き方をします。
{更新対象のレコード | 指定したフィールド = 更新したい内容}
つまり、contentのフィールドの中身を任意の文字に更新するという意味を表しています。

表示

view : Model -> Html Msg
view model =
    div []
        [ input [ placeholder "何かを入力してください", value model.content, onInput InputContent ] []
        , div [] [ text "入力した物は以下にも表示されます。" ]
        , div [] [ text model.content ]
        ]

HTMLへ変換する箇所です。
HTMLを表す要素(element)は、
関数 第一引数 第二引数
で構成されていて、各引数は配列になっています。
第一引数の配列には、属性やイベントを表していて、複数属性の場合はカンマ区切りで書きます。
第二引数の配列には、要素の中身を表していて、複数の中身はカンマ区切りで書きます。

例:
<div id="main" class="style" onClick="hoge()">test</div> であれば、
div [id "main", class "style", onClick hoge] [text "test"]
という風になる。

このサンプルでは、入力ボックスにテキストを入力すると、
onInputの処理が動作し、modelのcontentにデータをセットします。
セットしたデータ参照するために、value属性にmodelのcontentを表示するように書きます。
また div [] [ text model.content ]というところでも表示するようにします。
ちなみにこういった入力が即時反映されている状態を双方向データバインディングと呼んだりするが、特に呼び名は気にしなくていいです。

まとめ

長々と書きましたが、初めてやる言語で「おまじない」や「魔法」のような宣言を理解できたと思います。
一回では理解できないと思いますが、興味持ってElmを勉強し始めて少したった後に、復習としてこの記事を見返すと、気づきもあり、より理解が深まると思います。

また色々記事にしていきますので、乞うご期待を。