JavaScript
altjs
reactjs
Elm
redux
ElmDay 9

Elmというミニマムでフレームワークにもなる関数型altJS言語を触ってみよう!!!

More than 1 year has passed since last update.

Elm触ってみると面白くてハマりまして、この記事もそんな一人による紹介記事になります。

この記事が一番ストック数が多いので、v0.17になったことに合わせて書き直しました。(2016年5月18日)

Elmとは

・コンパイルすると、HtmlやCSSやJavascriptになる。

Elmとは、コンパイルするとHTML、CSS、Javascriptを生成するプログラミング言語です。

・Elmアーキテクチャで書ける。

v0.17から正式にElmはフレームワークを兼ね備えた言語になりました。
後述するElmアーキテクチャというパラダイムでブラウザアプリケーションを書きます。

・Elmはシンプルで小さな関数型の言語です。

ElmはHaskellやいろんな関数型言語を参考にして、とても小さく強力な構文を持っています。

・学習が始めやすいです。

かなり学習のしやすさや学習曲線を大事にしているのでちょっと見てほしい。

・FRP?なんのことかな

v0.16以前のElmには組み込みのFRP(Signal型)がありましたが、v0.17でなくなりました。ElmといえばFRPだったのですがもう関係ないので忘れてシンプルな言語と思って挑んでください!

・パッケージ管理システムや、ブラウザエディタもある。

ツールや学習環境といった周囲の環境と発展しているのもElmの特徴で、Elmをインストールすると、パッケージ管理システムや、ファイル監視サーバー、対話環境、がインストールされます。

Elmの公式サイトhttp://elm-lang.org/Install.elmで、ブラウザ上でElmを試すことが出来ます。

・コミュニティと対話しながら作っている。

なのでバージョンアップ毎に大きく変わります。

ではさっそくインストールしてみましょう。

Install

MacとWindowsは、インストーラーが公式サイトから公開されています。

公式サイト

またnpmからも公開されていますので、nodeを使っている方は下記のコマンドでもインストールできます。

npm install -g elm

windows環境の場合でうまくいかない時の注意です。
コマンドプロンプトの設定文字コードを変更してください。以下のコマンドをプロンプト上で実行します。

chcp 65001

インストールできたか確認します。

elm-make -h

コマンド説明が出るとうまくいってます。

インストールされるツール

このインストールで以下のツールが使えるようになります。

elm-make
依存関係を解決してelmファイルをコンパイルします。
デフォルトでhtml出力かな?js出力は--outputオプションの拡張子で指定します

elm-package
パッケージ管理システムです。

elm-repl
対話環境です。

elm-reactor
ファイルの監視と自動ビルド、デバッグ機能があるサーバーです。

Hello World

早速Hello worldを書いてみましょう。公式サイトにも載っています。

インストールしたツールとコマンドを使います。
まず使うコマンドは、elm-packageです。elm-packageはパッケージをインストールしたり公開することが出来ます。

elm-lang/htmlというパッケージを、ローカルのプロジェクトにインストールします。
elm-lang/htmlライブラリは、Elmにおいて基本的なHTML表示用のライブラリです。中身は仮想DOMになっています。

以下のコマンドをプロジェクトディレクトリ下で実行してください。

elm-package install elm-lang/html -y

メモ:実行すると、elm-stuffというフォルダと、elm-package.jsonというファイルが作成されます。
elm-stuffフォルダにはパッケージがインストールされています。
elm-package.jsonはパッケージとの依存関係が書かれています。

次にMain.elmというファイルを作ります。

Main.elm
import Html exposing (Html,div,text)


main : Html a
main = div [] [text "hello world!!"]

elm-makeコマンドでこのファイルをコンパイルします。

elm-make Main.elm

出来たindex.htmlファイルをブラウザで開きます。するとhello worldと表示されたと思います。

簡単にhtmlファイルを作ることができました。

絵を書く。

他に絵を描くように遊べるのがevancz/elm-graphicsパッケージです。v0.16以前のElmentCollage(canvasのラップ)があります。

またマークダウン記法で書きたいならevancz/elm-markdownがあります。

svgを書くならelm-lang/svgです。

Material Designならdebois/elm-mdl。
Material Iconsならelm-community/elm-material-icons

といったパッケージがあります。

Htmlアプリケーションを書く

Htmlの関数を使いHtmlを書きました。

次に、ある程度の機能や画面を総合した「アプリケーション」(またはコンポーネント)を書く方法を説明します。

HtmlパッケージのHtml.Appモジュールの関数を使います。

以下はテキストフィールドに入力すると、文字の部分がリアルタイムに画面に出て、
エンターキーを押すと、そのフィールド上にリストで表示されていく、というよくわからない例です。

import Html exposing (Html,div,input,text,li,Attribute)
import Html.App exposing (beginnerProgram)
import Html.Events exposing (on,keyCode,onInput)
import Html.Attributes exposing (type',value)
import Json.Decode as Json

main : Program Never
main =
  beginnerProgram { model = model, view = view, update = update }

type Msg = Input String | Fail | Enter

type alias Model ={list : List String, value : String}

model : Model
model = {list=[],value=""}


-----Update

update : Msg -> Model -> Model
update msg ({list,value} as model) =
  case msg of
    Input str -> {model | value = str}
    Enter -> {model | list = list ++ [value]
                    , value = ""}
    Fail -> model


---view

view {list,value} =
  div []
    [ listStr list
    , yourName value
    , textField value]


listStr model =
  let toList a = li [] [text a ]
  in div [] <| List.map toList model

yourName value =
  div [] [text <| "こんにちは " ++ value ++ " さん!"]

textField v =
  input [ type' "text"
        , onInput Input
        , value v
        , onEnter Fail Enter ] []


onEnter : msg -> msg -> Attribute msg
onEnter fail success =
  let
    tagger code =
      if code == 13 then success
      else fail
  in
    on "keyup" (Json.map tagger keyCode)

エントリポイントを見てみます。
Html.AppのbeginnerProgramという関数を使っています。

import Html.App exposing (beginnerProgram)

main : Program Never
main =
  beginnerProgram { model = model, view = view, update = update }

beginnerProgram 関数は引数にmodel、view、update関数をとります。
modelは状態の初期値、
viewは状態を画面に出す関数、
updateは(画面をクリックするなどして)イベントが起きた時の処理を書きます。

これらの関数を用意します。(とくに処理などがなければ、そのまま返すだけの関数でよいです。)

Html.Appにある関数は、Elmアーキテクチャという考え方にそっています。

このmodel、view、updateというapiでモジュール化して分割しよう。というのがElmアーキテクチャという構造化手法の基本形になります。

すこしコードを例に見てみます。
おおまかには、ユーザーの操作でviewでイベントが起きる→updateでそのイベント毎の処理をする→また画面に表示する→最初に戻る、という流れになってます。

Msg

type Msg = Input String | Fail | Enter

この部分ではMsg(Message)というアプリケーション内で起こるイベントの型を定義しています。(構文Union Typeで)
名前はたぶん何でもいいんですが、Elmアーキテクチャという考え方で統一されているので可読性などから、Msgとしています。以降のmodelやviewも同様です。

上記例では、入力時の文字列 | 失敗 | エンターキー押した時、という感じです。

Model

Modelでアプリケーション中の状態に当たる部分を定義します。

type alias Model ={list : List String, value : String}

model : Model
model = {list=[],value=""}

{list : 文字が並ぶ部分の状態、value : text inputの状態}
を定義しています。(レコード表記という構文で)

model=は初期値を設定しています。

update

update : Msg -> Model -> Model
update msg ({list,value} as model) =
  case msg of
    Input str -> {model | value = str}
    Enter -> {model | list = list ++ [value]
                    , value = ""}
    Fail -> model

update関数で、定義したMsgが起きた時の動作を定義します。(構文はパターンマッチとcase式)
Massage(イベント)とModelを受け取って新しいModelを返します。(構文はレコード表記)

update関数内だけでしかmodel(状態)を変更することは出来ません。

view

---view

view関数でmodel(状態)を画面に表示します。

Elmアーキテクチャ

ElmアーキテクチャとはElm言語上での構造化を行うときの手法、考え方、書き方指南のようなものです。
こちらのドキュメントが元になります。https://github.com/evancz/elm-architecture-tutorial

ある程度の機能のモジュール、コンポーネントを作成したら、init、model、view、update、Msg、Modelのような関数、型にわけ公開します。
このように公開すると、Elmの機能を使い木構造的な階層に構成することが出来ます。

parentConponent.elm
--親を作るのに子を使っている例


import ChaildConponent1
import ChaildConponent2

type alias Model = {...
                   , content1 :: ChaildConponent1.Model
                   , content2 :: ChaildConponent2.Model
                   }  

type Msg = ...
            | Hoge ChaildConponent1.Msg
            | Huga ChaildConponent2.Msg

そして一番親の関数と型を、用意されているHtml.AppのbeginnerProgram関数や、program関数に渡します。(一番上の層まで同じ構造になります。)

新しいモジュールを作るときや、他者が作ったモジュールを使って新しいアプリケーションを作るとき、容易にスケールさせることが出来ます。

メモ:ElmアーキテクチャはElm内だけにとどまらず、ReduxやCycle.jsから参照されています。
Elm言語はv0.16以前はSignal(FRP)を使って仮想DOMで描画するので、Elm+ElmアーキテクチャはReduxやCycle.jsと同じでした。
今のv0.17からはSignal(FRP)はなくなり、Cmd/Sub(Actor)という概念になりましたが、Elmアーキテクチャは健在でより記述が簡単になりました。

2つのパターン

Elmアーキテクチャには2つのバージョンが有り、それぞれbeginnerProgram関数とprogram関数に対応しています。

一つは基本形で、以下の様な関数、型を用意します。
シンプルでわかりやすいのでまずこちらで慣れるといいと思います。

type alias Model = ...
type Msg = ...

model : model
view : model -> Html msg
update : msg -> model -> model

もう一つはCmd/Sub(旧Effects)が入ったバージョンです。
Cmd/Subとは(例えばDBアクセスのような)非同期処理や並行処理や副作用処理を表しています。
Cmd/Subバージョンは基本バージョンを含むので、実用的なアプリケーションならこちらを採用します。

type alias Model = ...
type Msg = ...

init : (model, Cmd msg)
update : msg -> model -> (model, Cmd msg)
view : model -> Html msg
subscriptions : model -> Sub msg

modelinitに代わり、initupdateが、ModelCmd Msgをタプルで返すようになっています。
Cmdは「Elm内から起きる」非同期、副作用処理を表しています。(initにあるのはElmがロードされたタイミングに、updateにあるのは何かイベントが起きたタイミングに実行されます。)

Cmd/SubSubとは、「外からElm内に」入ってくるものを指します。一定のタイミング(Time)であったり、マウスなどのインターフェイスの入力であったりします。
アプリケーション全体に影響するので、program関数に入れます。

Cmd/Subバージョンの例として、マウスの座標を画面に表示するというのをやってみます。

マウスの座標を表示する

マウスについてのパッケージはelm-lang/mouseです。elm-packageでインストールします。

elm-package install elm-lang/mouse -y

中を見るとマウス座標はPosition型といい、
連続してPositionを返す関数はmoves関数と言うようです。

type alias Position =
{ x : Int
  , y : Int
}

moves : (Position -> msg) -> Sub msg
moves tagger =
  subscription (MySub "mousemove" tagger)

moves関数はSub msgという型を返します。
Subという型はprogram関数のsubscriptionsに渡します。
なので以下のようになります。

main : Program Never
main =
  program { init = init
          , view = view
          , update = update
          , subscriptions = \_ -> moves Move } --subscriptionsはModel -> Sub Msg。ラムダ式で達成してる。

type Msg = Move Position

最終的に以下のコードになります。


import Mouse exposing (moves,Position)
import Html exposing (Html,text)
import Html.App exposing (program)

main : Program Never
main =
  program { init = init
          , view = view
          , update = update
          , subscriptions = \_ -> moves Move }

type Msg = Move Position
type alias Model = Position

init : (Model , Cmd Msg)
init = {x = 0, y = 0} ! []

update : Msg -> Model -> (Model,Cmd Msg)
update msg model =
  case msg of
    Move position -> position ! []

view : Model -> Html Msg
view model = text <| toString model

!はPratform.Cmdの関数で、modelとCmdをタプルにしてくれる便利関数です。

(!) : model -> List (Cmd msg) -> (model, Cmd msg)
(!) model commands =
  (model, batch commands)

今回実行したいCmdはないのですべて空のリスト(何もなしになる)になっています。

init : (Model , Cmd Msg)
init = {x = 0, y = 0} ! []

公式サイト

http://elm-lang.org
公式サイトにはサンプルやドキュメントが豊富に用意されています。ブラウザエディタ上でElmを試すこともできます。

公式サイトの各ページの説明

公式サイトには文章が揃っているので探検してみよう。
examples
作例があります。

docs
言語に関するドキュメントやお知らせ。
構文やパッケージデザイン、スタイルガイドなどがあります。

community
Mailing ListやSlackへのリンクがあります。

Blog
今までのElmの各バージョン発表時のドキュメントなんかがあります。

構文

http://elm-lang.org/docs/syntax
Haskellに似ているので、覚えるのにHaskellのチュートリアルサイトとか参考にするとよいです。

代数的データ型、Union Type、レコード表記

type Msg = Add String | Error   --Union Type

type State = State (Int,Int)    --並べた型

type alias Position = (Int,Int) --名前の置き換え

type alias Model = {test : String}  --レコード表記

type alias Model = Model {test : String} --カプセル化した書き方

パターンマッチ

Haskell由来の強力なパターンマッチがつかえます。

type Msg = Add String | ...

update : Msg -> Model -> ...
update msg model =
       case msg of
           Add hoge -> hoge ...  ----hogeを取り出している。

view : (Int,Int) -> Html msg
view (x,y) = asText x   ---引数部分でパターンマッチを行い、xだけ取り出している。

step : Zahyou -> ...
step {x,y} = ------レコード形式の型もパターンマッチ出来る!

case式もif式もlet式もラムダ式も式です。関数の書けるところに書けます。
if式は条件分岐に、case式は型のパターンマッチに主に使います。

レコード構文

新しい型を簡単に書けるレコード構文があります。
幾つか前のバージョンでHaskellと全く同じになりました。

関数の適用順、パイプ演算子

Haskellの($)にあたる(<|)と逆の(|>)があります。関数を掛ける順番を右に左に自由に書けます。

dot' =
  circle 10
    |> filled blue
    |> move (20,20)
    |> scale 2

Haskellとの違い

Haskellとの違いについて

  • まずdo構文と、型クラスがないです。

  • Elmは正格評価です。

  • 型定義はtypetype aliasです。

type Bool = True | False
type alias Name = String
  • 型の明記はコロンです。リストの表現は :: です。
answer : Int
answer = 42
1 :: [2,3,4]
  • headとtailがMaybeを返す。
head : List a -> Maybe a
tail : List a -> Maybe (List a)
  • where無いletだけ。

  • 関数を並べるタイプのパターンマッチはない。case式使う。

Jsとの連帯

javascripとやり取りすることも出来ます。

JSファイルとして出力、読み込む

まずElmのMainになるモジュールの先頭行に、モジュール名をつけます。

Example.elm
module Example exposing (...)

次にJsファイルとしてコンパイルします。

elm-make Example.elm --output=Example.js

そしてHtmlファイルを用意して、出来たJSを読み込んで起動するコードを書きます。

...
<script type="text/javascript" src="Example.js"></script>
...

var elmApp =  Elm.Example.fullscreen()

これでElmが起動します。

呼び出し方は3つあり
fullscreen --全画面
embed --どこかのDOM内で展開
worker --画面なし

DOMに紐付けて起動する。

embedを使うとどこかのDOMにElmを展開できます。
tutorialから拝借したコード。

<div id="my-thing"></div>
<script src="my-thing.js"></script>
<script>
    var node = document.getElementById('my-thing');
    var app = Elm.MyThing.embed(node);
</script>

Elmアプリケーションの初期値をjavascriptから渡す

サーバーが生成するトークンを受け渡すとか、Elm内から用意できない初期値をJS側からElmに渡せます。

var elmApp =  Elm.Example.fullscreen({userId : 42})

この場合Html.AppのprogramWithFlags関数を使います。

main : Program { userID : Int}
main = programWithFlags { init = init
                        , ...

するとinit関数の引数にレコード型で渡ります。

portを使ってJSとのやり取りする

Elmの外のjsから受け取ったり、送るのに使うのが、portです。
portを付けて関数を定義します。

--Sub 主に外からElmに影響があるもの

port userID : Sub String 

port prices : (String -> msg ) -> Sub Float -- msg用の値を受け取れる

--Cmd はElm内から発生するコマンド

port hoge : Cmd Float       
port hoge =  ...

update msg model =
       case msg of
         ... -> model ! hoge

// TODO:

portは前のバージョンでは、mainモジュール以外だと書けなかったが今は書けるようになった。
portを使っているパッケージは公開できない。(同時にjsのインストールも必要になるため)

Nativeモジュール

coreライブラリ内を見ると、Nativeというフォルダがあります。これがNatveモジュールで、直接Elmランタイムの中にJavascriptを展開するように書くことが出来ます。

便利ですが、将来的にランタイムが壊れやすくなる要素を組み入れたくないElmはNativeモジュールを非推奨にしています。
とはいえ、すぐにJavascript用apiをElmで叩いたり必要になったりもします。

やり方TODO

影響がElmに及ばないように、Effectモジュールという方法もあります。(まだよくわかってない)

以下メモ

Debug

import Debug(log,watch)

hoge = Debug.log "targetは" <| target.....

Debugライブラリのlogという関数はasTextのコンソール版です。
引数の文字列と一緒にコンソールに表示します。デバッグに便利です。

エディタ

主要なエディタには、シンタックスハイライトのプラグインがあるようです。

自分はatomを使っています。atomのElm用プラグインはモジュール内を検索し、関数とコメント部分を表示する機能があるので便利です。

(sublime,atomの場合、)Ctrl+pでプロジェクト内のファイル検索です。インストールしたElmパッケージをファイル検索して読むと、学習が捗ると思います。

よく最初ミスったとこ

let の中の変数の先頭と、case式のパターンの先頭は揃えないとコンパイラが読み取らない。

main =
       let hoge =
           huga =
       in ...

case state of
    Nomal   ->
    Add   x ->
    _       ->

その他

  • リスト内包表記無い。

  • 正格評価ですべて型付けされる。whereをなくしてletだけにして高速化している。

  • v0.16から末尾再帰は最適化されるようになった。

  • 代数データ型は引数のある関数のように扱えます。以下はよく見る入力の型を作る場面

data Action = Add Int | Nomal | ...

action : Signal Action
action = Add <~ count Mouse.clicks

パッケージ

パッケージのサイト
http://package.elm-lang.org/
いろいろある、主要なのは

描画

evancz/elm-graphics
evancz/elm-markdown ---マークダウン
elm-lang/svg ---svg
debois/elm-mdl  ----Material Design
elm-community/elm-material-icons ---Material Icons

アニメーション

mdgriffith/elm-style-animation

ルーター

elm-navigation

日付フォーマット

mgold/elm-date-format

パーサーコンビネータライブラリ

・Bogdanp/elm-combine
・Dandandan/parser、

線形代数

その他

・evancz/focus 
レコードが3つ以上ネストした時はfocusライブラリかElmアーキテクチャを検証する。

参考になるリンク集

参考になるページを集めてみました。

公式サイト

公式ホームページ

公式ホームページ。ドキュメントやサンプルコードを見ることが出来ます。

Introduction · An Introduction to Elm

ドキュメントの中にある、ElmとElm-Architectureについて説明したガイドページ。

evancz/elm-todomvc: Proper implementation of the TodoMVC app

Elmで作られたtodoリストのサンプル。

コミュニティ

Join Elm on Slack!

Slack、頻繁に人がいる。

Elm Discuss - Google グループ

メーリングリスト

elm-dev - Google グループ

ライブラリなど、製作者向けML What is elm-dev for? - Google グループ

Tokyo Elm Programming Meetup (東京) - Meetup

tokyo elm meetup

Tutorial

Elm Tutorial

チートシート等

izdi/elm-cheat-sheet 

Elmチートシート

Elm FAQ

FAQ形式でElmについて

ブログ、記事

NoRedInk Tech

Elmをいち早く取り入れた企業noredinkのブログ。

ジンジャー研究室

Elmのツールなどをすごいたくさん開発しているjinjorさんのブログ。
おすすめの記事
再利用可能なコンポーネントはアンチパターン - ジンジャー研究室

CSS in JS(Elm)したら想像以上に良かった - ジンジャー研究室

「Elmでやってみる」シリーズのまとめエントリ - uehaj's blog
Elmを最初期から追っているuehajさんの記事。

Elm | POSTD
PostdのElmタグの記事。

Elm Advent Calendar 2014 - Qiita
Elm Advent Calendar 2015 - Qiita
これでElm知った人も多いアドベントカレンダー。次のアドベントカレンダーは君も書いて。

まとめ

Elmはデザインが(好き嫌いではなくちゃんと測れるものとして)とても良いと思います。学習のしやすさへの苦心や、構文や技術の選択がこれは人類つかうやろうなぁと思います。

Elmが解決する問題領域はGUIです。複雑怪奇になりそうなその領域に、Elm+Elmアーキテクチャというのを生み出しました。

Elmは純粋静的型付け関数型の構文を採用しています。ブラウザが仕様に仕様を重ねて例外だらけみたいなイメージなので純粋と合わないのではと、初めて見た時思いましたが関係ないです。コンパイルする時の話です。

キャッチコピーとして、Elmでコンパイルして出来たランタイムは壊れません。そのために言語側からと、ランタイム側から頑張っています。(生成されるJSは古い環境にも対応したものだったり、Maybe型を返したり、caseで網羅していないと怒るようにしたり)

Elmのデザインで好きなのが、思い切ったユーザーへの制限です。そのおかげでミニマムです。個人的にこのくらいのフレームもいい。

Elmはバージョンが上がるたびに破壊的な変更が入っています。古いバージョンは使ってられませんので、バージョンアップを追える人が望ましい。ロードマップ的なものはあるのかないのかという感じです。

コンパクトで、見た目もスッキリしていて、簡単にブラウザでビジュアルになるので、Elmは面白い言語です。
Html勉強した人がそのままElmでプログラミングを覚える、ということもできると思ってます。

ぜひElmやってみてね。

まとめ。Elmのデザインと、おすすめする人。しない人。

Elmのデザイン おすすめする人。
ブラウザ開発言語 フロントエンドの人。html、css、js知っている人。
学習が容易。すぐ画面になる。絵を作れる。 プログラミング初心者に。関数型言語の勉強に。
新しい言語 人に言語を覚えてもらうのは、思っている以上に難しい。少人数、小さなツールからがおすすめ。>electron
バージョンアップで変わる。 毎日ネット技術を追っている人がいいかもしれない。公式が発表する時にバージョンアップ情報確認するのが出来る人。
構文がHaskell 構文の情報はHaskellの方が多いので、Elm勉強時はHaskellの勉強も行うことになるので、Haskellへの入り口に。Haskell知っている人。Haskell勉強したい人。
純粋、静的、関数型言語 コンパイルする時に型の違いでコードの間違いを指摘される、出力されたランタイムは壊れることがない。そのあたり響く人。
ユーザーへの制約 コード内はなるべく手続き的な記述はできなくなっていたり、状態の更新がupdateだけだったり、型クラスがなかったり(これは先変わるかも)、Elmアーキテクチャオンリー、こういうスケールがいい人。いやなら別の言語(purescrip、ClojureScript、scalaJS、ghcjs...etc)がいいかも
ドキュメントは英語 でも自分はドキュメントも、Mailing Listも翻訳機能使って読んでる。