読者です 読者をやめる 読者になる 読者になる

きくらげ観察日記

好きなことを、適当に。

Elm始めてみました。

Elm

Elmという言語を一ヶ月ほど触ってみたので、この言語の特徴などを紹介したいと思います。

Elmとは

http://elm-lang.org/

この言語はざっくりと言うと、「JavaScriptコンパイルされる、純粋関数型でHaskellっぽい文法の言語」です。

純粋関数型

ElmはHaskellと同様に純粋関数型(と通常はみなすことができる)言語です。基本的に、Elmの中ではどのような副作用も起こすことはできませんが、後述するThe Elm Architectureにより外界との通信などを行うことができます。

Elmは関数型言語に特徴的な機能のほとんどを持っています。

-- 代数的データ型
type Hoge = Hoge | Fuga

-- レコード型
type alias Person =
    { name : String
    , age : Int
    , address : String
    }

-- パターンマッチ
toStr : Hoge -> String
toStr hoge =
    case hoge of
        Hoge -> "hoge"
        Fuga -> "fuga"

また、MaybeやResult(Eitherに相当)など、関数型的プログラミングをするために必要な型も一通りそろっています。
文法はかなりHaskellに近いですが、正格評価だったり型クラスがなかったりするので中身はあまりHaskellっぽくありません。むしろOCamlに近いかもしれません。

The Elm ArchitectureによるIOの実現

Elmは純粋関数型言語ですが、モナドも一意型もありません。それではどのようにして副作用を実現しているのかというと、FRPの概念を元にして作られたThe Elm Architectureというフレームワークを利用します。基本的に、全てのプログラムはModel, Msg, view, update, subscriptionsの5つからなります。

type Model = (アプリケーションの状態管理に使用するデータ型)
type Msg = (外界との通信に必要な型)
init : (Model, Cmd Msg) -- 初期状態
view : Model -> Html msg -- 表示
update : Msg -> Model -> (Model, Cmd Msg)
subscriptions : Model -> Sub Msg

Model, viewについての説明は不要だと思いますが。Elmには見慣れないupdate, subscriptionsという2つの関数があります。
The Elm Architectureの考え方の根本はFRPなので、外界で発生するイベントは時系列に並んだリストのようなものと考えることができます。updateはこれらを畳み込んでModelを変化させていく関数です。

update (マウスクリックを表すイベント) model = (イベント処理の終わったmodel, cmd)

updateは更新後のmodelの値の他にCmd Msgという謎の値も返していますが、これは外界への命令を表します。外界への命令というのは、例えば「hogehoge.comにGETリクエスト送っといて」とか「ランダムに整数生成しといて」といったIOの絡むものを表します。これらを表すCmd Msg型の値を返すと、その結果がMsg型となって後のupdateの引数に渡されていきます。

もうひとつの関数subscriptionsは、マウスのクリックや時間の経過等無数にあるイベントのうち、どのイベントをupdateの引数として通知して欲しいかを表します。

これらの関数を利用して、IOの必要な処理をCmdとして外界に送信し、返ってきた値をupdateの引数として受け取り、さらに処理を続けていく…といったループが、Elmのプログラムの基本的な書き方となります。

Taskによる非同期処理

Elmではhttpリクエストの送信などの非同期で行いたい処理をTaskという概念を用いて行います。
TaskはTask.attemptという関数によりCmdに変換され、それをupdateで送信することによって実行後にMsgとなって返ってきます。

type Msg = Users (List String) | ...

-- api.example.com/users にリクエストを送るタスク
getUsers : Task Http.Error (List String)
getUsers = Http.get (list string) "http://api.example.com/users"

こうしてできたタスクは、Task.performという関数によりCmdに変換され、updateを使って送信されます。
そして、返ってきた値がMsgとなってupdateに渡されるという流れになっています。

portによるJSとの連携

JSで書かれたコードとのやり取りは、portという機能を介して行われます。
ポートには入力ポートと出力ポートの2種類があり、それぞれ次のように定義します。

-- 出力ポート
port outPort : String -> Cmd msg
-- 入力ポート。引数を使ってポートからの値をmsgに変換する。
port inPort : (String -> msg) -> Sub msg

Stringの部分はJSとのやり取りに指定したい型です。別にStringじゃなくても構いません。
このようにして定義したportを、先ほど説明したupdateにより外界に渡し、subscriptionsにより受け取ることができます。
Elmとやり取りするためのJSのコードは以下のようになります。

var app = Elm.Main.fullscreen();
// 出力ポートからの値の受け取り
app.ports.outPort.subscribe((str) => {
    (適当な処理);
    app.ports.inPort.send('何らかのメッセージ')
});

これだけです。簡単ですね。
この機能により、容易にJavaScriptで書かれたコードとの連携を取ることができます。そのため、JSで書かれたアプリケーションの一部にElmを使ったり、その逆を行うことも可能です。


以上がElmの主な特徴になります。
FRPを前提とした言語というのは他では聞いたことがありませんし、一度やってみるのも面白いのではないでしょうか?

次回はElmに関する愚痴とかを書きます。