Studyplus Engineering Blog

スタディプラスの開発者が発信するブログ

Elmの何が良いのか?何ができるのか?

こんにちは。ForSchool事業部の石上です。お菓子はばかうけが好きです。今日はElmの話です。

背景

Studyplus for Schoolには、Elmで実装された画面アプリケーションがあります。こういうやつです。

仕様はとても小さく、QRコード読み取る -> APIへ投げるという機能のみだったため、Elmでの実装が許されました。今回は、Elmを普段触っていないチームメンバーに「なんでElmなんて使ってるんだ...?」と思われないように、その良さを伝えておきたいと思います。

Elmの特徴

まずElmについて簡単に書いておきます。Elmの特徴は主に3つでしょう。

The Elm Architecture

The Elm Architectureを構成する要素は、ModelとViewとUpdateです。Modelはアプリケーションの状態を表すデータ構造、ViewはDOMを出力する関数、Updateはイベント1に対して状態を変更する関数です。

Redux経験があれば、あれとほぼ同じものと考えて良いと思います。個人的にReduxとTypeScriptを使う場合と比較して好きなのは、ReduxとTypeScriptだとアクションの型定義が面倒だったり工夫が必要だったりするところ、Elmでは type Msg = MyMsg Payloadのように書けて、記述しやすいのが好きです。

Runtime Errorが起きない

Elmは静的型付け言語です。基本的には2コンパイル時に不正な関数呼び出しを検出することができます。

コンパイルエラーが親切

コンパイルエラーが親切なのもElmの良いところです。たとえば、あるモジュール間で循環参照がある場合に、以下のようにわかりやすいエラーになります。

./src/Main.elm
Error: Compiler process exited with error Compilation failed
Compiling ...-- IMPORT CYCLE ----------------------------------------------------------------

Your module imports form a cycle:

    ┌─────┐
    │    Note
    │     ↓
    │    Main
    └─────┘

Learn more about why this is disallowed and how to break cycles
here:<https://elm-lang.org/0.19.1/import-cycles>

Your module imports form a cycle と言われて意味がわからなくても、この図を見れば、「ああ、MainがNoteをimportして、NoteがMainをimportしているからぐるぐるしちゃうんだな」というのがわかります。そして、必ずと言っていいほど最後に参考リンクが載っています。

SPA化

Elmに限らず、ウェブアプリケーションをSPA(シングルページアプリケーション)にするときには、リンクを少し拡張するような仕組みが必要になります。通常のHTMLのaタグであれば、リンクされたURLへ遷移する際、ドキュメント(HTMLなど)をすべてダウンロードしてブラウザに表示します。しかしSPAでは、それを行わずに、必要なリソースを必要なときに取得しつつ、画面の必要な部分を更新するというようなことをします。雑に書くと以下のようなイメージです。

<script>
const handleSPALink = (e) => {
  e.preventDefault(); // 通常の遷移をしない
  updateState(); // 状態を更新
  render(); // 表示
}
</script>
<a onclick="handleSPALink" href="/hoge">SPA Link</a>

ReactであればReact Routerが使われることが多いです。

Elmの場合、Browser.application という関数を使います。

  1. Browser.applicationの引数に以下のようにメッセージを設定
    1. haskell Broser.application { ... , onUrlRequest = UrlRequest , onUrlChange = UrlChange }
  2. a要素の関数でa要素を表示
  3. a要素をクリックすると、 UrlRequestが発行される
  4. update関数でUrlRequestをつかまえる
    1. 内部リンク(Browser.Internal)の場合:Navigation.pushUrlでURLを変更
      1. UrlChangeが発行される
      2. URLに応じたページの初期化を行う
    2. 外部リンク(Browser.External)の場合:Navigation.loadで外部URLへ遷移

なんだか面倒そうですね。しかしこれには良いところもあって、それはページ遷移してURLが変わったという変更がちゃんとTEA(The Elm Architecture)のなかに収まることです。ReactとReduxの組み合わせで同様のことをやろうとすると、またそれ用のredux middlewareを設定してあげたりする必要がありそうなので、Elmではこういうことが標準の機能として用意されているというのが安心感があります。

詳しくは:https://package.elm-lang.org/packages/elm/browser/latest/Browser#application をご覧ください。

Port

Studyplus for Schoolで実装したアプリケーションではカメラやオーディオを扱う必要があったため、Elmだけでは実装が完結しませんでした。そこで、Portという機能を使う必要がありました。

Portとは、Elmの世界とJavaScriptの世界をきれいに分けて実装する仕組みです。公式のガイドには、localStorageを扱う例が書かれています。

JavaScript側

var app = Elm.Main.init({
  node: document.getElementById('elm')
});
app.ports.cache.subscribe(function(data) {
  localStorage.setItem('cache', JSON.stringify(data));
});

Elm側

port module Main exposing (..)

import Json.Encode as E

port cache : E.Value -> Cmd msg

上記のコードは、Elm側で起こったイベントを、JavaScript側で処理しています。これを使うには、Elm側のupdate関数のなかで以下のようにCmd.batchという関数にこのcache関数の返り値を指定します。

update msg model = 
    case msg of
        Cache value ->
            (model, Cmd.batch [cache value])

実装がElmだけで完結せず、JavaScriptの依存(package.jsonのことです)がたくさん入ってしまうのは残念なところですが、作りとしてはElmとJavaScriptがしっかり分けられるのは良いことだと思います。

テスト

Elmには、https://github.com/elm オーガニゼーションで管理されている標準のライブラリのほかに、https://github.com/elm-explorations で管理されている準標準ライブラリのようなものがあります3。そこにテストライブラリもあるので、基本的にはそれが使えます。

まとめると?

ごちゃごちゃと書きましたが、まとめるとどういうところが良いのでしょう。

  • SPAをつくるために必要なものがちゃんと標準ライブラリ(あるいは準標準ライブラリ)に入っている
  • 実行時エラーが起きにくい

逆に良くないところは、以下の2つです。

  • localStorageやAudioなどブラウザのAPIを直接触れないところが多く、そういうのが必要なところはJSのコードを書かないといけない
  • 学習コスト。特に型の読み方に慣れるまで大変。

アプリケーション実装にElmを使わなくても、Elmを学ぶことで恩恵はあると思います。たとえば、Elmを学ぶ前は、サーバーから来たデータをJSON.parseして型をanyにしてしまうことに、疑問を持ったことなどありませんでした。もしこの記事を読んで興味を持たれたら、仕事で使う予定がなくても、堅牢なフロントエンドを作る仕組みを知るためにElmに触れてもらえればと思います。


  1. これはElmの世界ではMsgと呼びます。ReduxではActionと呼んだりします。ライブラリによっていろんな呼び方があってややこしいですね。

  2. ElmからJavaScriptの関数を呼び出すようなこともできるため、絶対に起きないわけではありません。

  3. オーガニゼーションの説明にはPackages that may one day be in @elm-langと書かれています