Elmは、JavaScriptにコンパイルされるフロントエンド向けの関数型プログラミング言語です。英単語の elm は「ニレの木」を指しますが、ここで扱うのはEvan Czaplicki氏が2012年に公開した言語のほうです。長く更新が止まっていましたが、2026年7月6日に約6年8か月ぶりとなる0.19.2が公開され、1.0へ向けたロードマップも示されました。この記事では、0.19.2で何が変わったのか、The Elm Architectureと型システムが実際にどう働くのか、そして2026年時点でElmを採用してよい条件と外すべき条件までを、公式の一次情報と手元での実行結果に基づいて整理します。
まとめ:Elmの現在地と採用判断の要点
結論から述べます。Elmは止まっていませんが、速くもありません。2026年7月6日の0.19.2は0.19.1(2019年10月21日)以来のリリースで、内容はコンパイラの性能改善のみ、言語仕様の変更はゼロでした。標準ライブラリの elm/core は2020年2月15日から更新されていません。一方で作者は「小さな非破壊リリースを重ねて1.0へ向かう」と明言し、equatable・hashable 型の追加を予告しています。リポジトリには次期版の 0.19.3 ブランチがあり、2026年8月25日にもコミットが入っています。
採用判断は、JavaScript資産の量で決まります。地図・チャート・決済SDKなど既存のJSライブラリに依存するUIでは、Elmは ports というひとつの境界を通してしか外部と通信できず、ラッパーを書く工数が先に発生します。逆に、フォームと画面遷移が中心で5年以上動かす業務系SPAなら、コンパイラが全参照を検査するElmのリファクタリング耐性が効きます。以下で0.19.2の中身、TEAの構造、型システムの実際の挙動、そして他の選択肢との線引きを順に見ていきます。
Elm(プログラミング言語)とは:実行時例外を出さない設計のフロントエンド言語
Elmは、ブラウザで動くUIを書くための言語であり、同時にアーキテクチャ・ビルドツール・パッケージ管理を内包した一体型の処理系です。公式サイトはElmを「親切なエラーメッセージ、高い性能、小さなアセット、そして実行時例外がないことを備えた快適な言語」と説明しています。ライブラリではなく言語そのものが状態管理の型を決めるため、React + 状態管理ライブラリ + 型チェッカという組み合わせを自分で選ぶ余地はありません。この「選べなさ」が、Elmの長所と短所の両方の出どころです。
特徴的なのはパッケージ配布の仕組みです。package.elm-lang.org は投稿されたパッケージの公開APIを型情報から機械的に比較し、破壊的変更があればメジャーバージョンの繰り上げを強制します。バージョン番号が人間の申告ではなく型検査の結果で決まるため、elm.json に書いた依存が意図せず壊れる事故が構造的に起きにくくなっています。
なお「実行時例外がない」という保証はElmで書いたコードの内側に限られます。後述の ports で受け渡した先のJavaScriptが投げる例外までは消えません。境界の外は従来どおり自分で守る必要があります。
0.19.2で変わった点と、6年8か月の空白が示すElmの現在地
0.19.2の中身:パーサのアロケーション削減による7%短縮
0.19.2の変更はコンパイラの性能改善に絞られています。作者の解説記事によれば、改善の実体はパース時のメモリ確保を減らしたことで、85万行規模のコードで「GCのコピー量が20%減、ピークメモリ使用量が10%減、全体で7%高速化」という数字が示されています。同記事は85万行のフルビルドを5.7秒、増分ビルドを350ミリ秒未満と報告しています。実プロジェクトでの計測は「わずかな改善から最大1.9倍まで幅がある」とされ、その上限側の例として351モジュールのフルビルドが4.981秒から2.595秒へ短縮した数字が挙げられています。同記事は50万行を超える規模で「ささやかな改善」が見込めると書いており、小規模なプロジェクトでは体感差はほとんどありません。
移行コストはほぼゼロです。リリースノートは「言語の変更はない」と明記しており、elm.json の "elm-version" を "0.19.2" に書き換えるだけで既存コードがそのまま通ります。配布物の名前は elm-0.19.2-mac-x64.gz のような形式に変わり、0.19.1にはなかった Linux の ARM 版バイナリが追加されました。npm の elm パッケージも 0.19.2-0 として同日に公開されています。
「オワコン」かどうかの判定材料:リリース間隔と1.0への計画
Elmの将来性を疑う検索が絶えないのは、数字を見れば当然です。2026年9月5日時点で確認できる事実を並べます。
| 項目 | バージョン | 公開日 |
|---|---|---|
| コンパイラ elm/compiler | 0.19.2 | 2026-07-06 |
| コンパイラ(その前の版) | 0.19.1 | 2019-10-21 |
| 標準ライブラリ elm/core | 1.0.5 | 2020-02-15 |
| elm/browser | 1.0.2 | 2019-11-03 |
| elm/html | 1.0.1 | 2025-11-12 |
コンパイラのリポジトリはstar約7,900、未解決のissueは279件(ほかにオープンなプルリクエストが25件)で、既定ブランチが master から main へ切り替わったのも0.19.2と同じ2026年7月6日でした。つまり6年以上のあいだ、実質的に開発の主線が別の場所にあったことになります。作者はその間、Acadiaというデータベース関連のコンパイラに取り組んでいたと述べており、そこで得た知見をElmへ順次取り込む計画として、非破壊の小さなリリースを重ねてから1.0を出す方針と、equatable・hashable といった型の追加を公表しています。0.19.2の公開後も作業は続いており、次期版にあたる 0.19.3 ブランチには2026年8月25日付で「switch to new Graph API」というコミットが入っています。
判定を言い切ります。Elmは開発が終了した言語ではありませんが、2026年時点で新規案件の第一候補に置ける段階でもありません。リリース間隔を根拠に「継続的にメンテナンスされている」と発注者に説明できないためです。既存のElmプロジェクトを維持する判断は妥当で、0.19.2への更新も安全に行えます。新規に選ぶ場合は、次のリリースが来なくても5年間そのまま運用できるかを先に確認してください。言語仕様が動いていないという事実は、裏を返せば陳腐化しないという意味でもあります。
The Elm Architecture:Model・Msg・update・viewで閉じる更新ループ
The Elm Architecture(TEA)は、アプリの状態を Model という単一の値にまとめ、起きうる出来事を Msg という型で列挙し、update が「今のModelとMsg」から次のModelを作り、view がModelからHTMLを組み立てる、という一方向の輪です。公式の0.17リリース記事はこのパターンについて「Reduxのような派生を通じてJavaScriptの書き方としても広まりつつある」と述べています。
Elmがリアクティブプログラミングの文脈で語られることがあるのは、0.17より前のElmが Signal という値を中心にしたFRP(関数リアクティブプログラミング)の言語だったためです。2016年の0.17で Cmd と Sub が導入され、公式記事の言葉を借りれば「signalに関するものはすべて、より単純で扱いやすいものに置き換えられ」ました。現在のElmを学ぶ際にFRPやSignalの知識は不要で、Web上に残る2016年より前の解説はそのまま適用できません。
Msg と update の役割はよく混同されます。Msg は「何が起きたか」を表すデータで、それ自体は状態を変えません。ボタンを押したという事実は Increment という値になってランタイムへ渡り、update だけがその値を受け取って新しいModelを返します。状態を書き換える関数がアプリ全体で1つに固定されるため、想定外の場所から状態が変わる経路が存在しません。この構造は判別可能なユニオン型(Discriminated Union)を言語機能として持つことで成立しています。
module Main exposing (main)
import Browser
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)
type alias Model =
{ count : Int }
type Msg
= Increment
| Decrement
init : Model
init =
{ count = 0 }
update : Msg -> Model -> Model
update msg model =
case msg of
Increment ->
{ model | count = model.count + 1 }
Decrement ->
{ model | count = model.count - 1 }
view : Model -> Html Msg
view model =
div []
[ button [ onClick Decrement ] [ text "-" ]
, div [] [ text (String.fromInt model.count) ]
, button [ onClick Increment ] [ text "+" ]
]
main : Program () Model Msg
main =
Browser.sandbox { init = init, update = update, view = view }
このカウンターを0.19.2の elm make --optimize でビルドすると、出力されるJavaScriptは109,959バイト、gzip圧縮後で26,509バイトでした。ランタイムを含んだ最小構成の実測値です。
Browser.sandboxとBrowser.elementの選び分け
上のコードが使っている Browser.sandbox は、外の世界と一切通信しないアプリ用の入口です。update の型が Msg -> Model -> Model と単純なぶん、HTTPリクエストもタイマーもJavaScript連携も使えません。
外部と通信する場合は Browser.element を選びます。init と update が ( Model, Cmd Msg ) を返すようになり、副作用を Cmd という値としてランタイムへ委譲します。加えて subscriptions で外から届く出来事を購読できます。既存のページの一部だけをElmで置き換える場合もこの Browser.element が起点です。ページ全体を担当してURLも管理するなら Browser.document と Browser.application が用意されています。まず Browser.sandbox で書き始め、通信が必要になった時点で Browser.element へ移すのが実務上の手順です。
型システムの実際:MaybeとResultでnullを持たない設計
Elmには null も undefined もありません。値が無いかもしれない場所は Maybe、失敗するかもしれない処理は Result という型で明示し、case 式で両方の分岐を書かないとコンパイルが通りません。0.19.2の elm repl で実際に評価すると、挙動は次のようになります。
> List.head []
Nothing : Maybe a
> String.toInt "42"
Just 42 : Maybe Int
> String.toInt "4x"
Nothing : Maybe Int
> Maybe.withDefault 0 (String.toInt "4x")
0 : Int
JavaScriptなら parseInt("4x") は 4 を返し、[].shift() は undefined を返して、その値が離れた場所でエラーになります。Elmは同じ場面を Nothing という値で返し、それを整数として扱おうとした時点でコンパイルを止めます。バグの発見位置が実行時のブラウザからビルド時のターミナルへ移る、というのがElmの型システムの効果です。
コンパイラのエラーメッセージが指す修正手順
Elmのエラーメッセージは、型の不一致を報告するだけでなく修正案まで書きます。整数を文字列に連結しようとしたコードを0.19.2でコンパイルすると、出力は次のとおりです。
-- TYPE MISMATCH --------------------------------------------------- src/Bad.elm
I thought I was appending String values here, not Int values like this:
5| "count: " ++ n
^
Try using String.fromInt to turn it into a string?
Detected problems in 1 module.
該当行と桁位置、期待した型、そして使うべき関数名(String.fromInt)まで一度に提示されます。型に不慣れなうちはこのメッセージがそのまま学習材料になるため、Elmが入門用の関数型言語として推される理由の実体はここにあります。
0.19.2の導入手順:バイナリ取得からelm initとelm reactorまで
導入方法は2通りです。GitHubのリリースページから各OS向けのバイナリ(elm-0.19.2-mac-x64.gz など)またはmacOS/Windows用インストーラを取得するか、Node.jsが入っている環境なら npm install -g elm でも同じ0.19.2が入ります。展開したバイナリに実行権限を付けて elm --version が 0.19.2 を返せば準備完了です。
プロジェクトの初期化は elm init です。実行すると src ディレクトリと elm.json が作られ、以降のソースコードは src 配下に拡張子 .elm のファイルとして置きます。0.19.2が生成する elm.json は次のとおりです。
{
"type": "application",
"source-directories": [
"src"
],
"elm-version": "0.19.2",
"dependencies": {
"direct": {
"elm/browser": "1.0.2",
"elm/core": "1.0.5",
"elm/html": "1.0.1"
},
"indirect": {
"elm/json": "1.1.4",
"elm/time": "1.0.0",
"elm/url": "1.0.0",
"elm/virtual-dom": "1.0.5"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}
書いたコードを見るには elm reactor を使います。開発用サーバーが起動し、ブラウザからElmファイルをクリックするとその場でコンパイルして結果が表示されるため、HTMLを自分で用意する必要がありません。本番用のJavaScriptを出力する段階で elm make src/Main.elm --optimize --output=main.js に切り替えます。この2つに elm repl・elm install・elm bump・elm diff・elm publish を加えた8つが、0.19.2が提供するコマンドのすべてです。ビルドツールを別途選ぶ必要がない代わりに、選択肢もありません。
JavaScriptとの連携:portsで境界を1本に絞る
Elmは0.19以降、コミュニティのパッケージがJavaScriptを直接呼ぶことを禁じています。外部との通信路は ports だけです。port module と宣言したモジュールで、送信用の Cmd を返す関数と、受信用の Sub を返す関数を定義します。
port module Ports exposing (Msg(..), received, save, update)
port save : String -> Cmd msg
port received : (String -> msg) -> Sub msg
type Msg
= Save
| Received String
update : Msg -> String -> ( String, Cmd Msg )
update msg model =
case msg of
Save ->
( model, save model )
Received newText ->
( newText, Cmd.none )
JavaScript側は app.ports.save.subscribe で値を受け取り、app.ports.received.send で値を送り返します。やり取りは非同期のメッセージパッシングで、JavaScriptがElmの内部状態を直接触ることはできません。この一本化のおかげで、JSライブラリが例外を投げてもElm側の状態は壊れません。
代償は工数です。地図やチャートのライブラリを使うたびに、値の送受信とエラー時の扱いを自分で設計することになります。既存のnpmエコシステムを前提にした画面をElmで作り直す判断は、この設計コストを見積もってからにしてください。関数型言語のコードをJavaScriptへ変換するという発想自体は珍しくなく、Haskell側の同種の取り組みはGHCJSとGHCのJavaScriptバックエンドとして整理しています。
React・TypeScript・GleamとElmの選び分けとElmを外すべき条件
Elmと比較されるのは、同じ問題に別の角度から答えるTypeScript + ReactとGleamの2つの対抗軸です。
| 観点 | Elm | TypeScript + React | Gleam |
|---|---|---|---|
| 型検査の範囲 | 言語全体 | 型注釈の範囲(anyで穴が開く) | 言語全体 |
| JS資産の再利用 | ports経由のみ | そのまま | FFI経由 |
| 状態管理 | 言語が規定(TEA) | ライブラリを選択 | フレームワーク次第 |
| 導入の粒度 | 画面単位 | ファイル単位 | プロジェクト単位 |
| 主戦場 | ブラウザUI | ブラウザUIと汎用 | BEAM上のサーバー |
既存のReactアプリに型安全性を足したいだけなら、答えはTypeScript 6.0とその移行です。ファイル単位で段階導入でき、npmの資産をそのまま使えます。React 19.2の新機能のような更新が年に何度も入る点も、発注者への説明のしやすさでは有利に働きます。関数型のパラダイム自体に関心があり、対象がサーバーサイドならErlang VM上で動くGleamが別の答えになります。Gleamは2026年8月1日にv1.18.1が出ており、リリース頻度という一点ではElmと対照的です。
Elmを外すべき条件のうち、最も重いのは外部SDKへの依存です。地図・帳票・決済などのJSライブラリが画面の中心にある場合、portsのラッパー設計が主作業になり、言語の利点を回収できません。次に効いてくるのが体制の問題で、要員を短期で入れ替える前提なら避けるべきです。日本語の一次情報は公式ガイドの翻訳が中心で、検索上位に並ぶ解説記事も0.19.1時代の記述で止まっています。SEOのために初期HTMLを返す必要があるサイトも対象外です。elm make が出力できるのはJavaScriptとHTMLだけで、サーバーサイドレンダリングの公式機能はありません。
逆に採用が合理的なのは、業務系の管理画面やフォーム中心の内製SPAで、外部SDKへの依存が薄く、同じチームが数年単位で保守し続ける場合です。仕様変更で型を1か所直せばコンパイラが影響範囲を全部指摘する、という性質は、テストの薄い長寿命コードほど効きます。
よくある質問
elmという単語の意味は何ですか?
英語の elm は「ニレの木」を意味する普通名詞です。プログラミング言語のElmはこの単語から名付けられていますが、命名の理由は elm-lang.org とコンパイラのリポジトリのいずれにも記載がありません。検索結果に樹木の解説と言語の解説が混在するのはこのためで、言語について調べる場合は「elm 言語」「elm lang」のように語を足すと目的の情報に届きます。
Elmの最新バージョンはいくつですか?
2026年9月5日時点の最新は0.19.2で、2026年7月6日に公開されました。手元の環境で確認するには elm --version を実行します。0.19.1からの更新は言語仕様に影響しないため、elm.json の "elm-version" を書き換えるだけで移行できます。
ModelとMsgはどう違いますか?
Model はアプリが現在保持している状態そのもので、Msg は「何が起きたか」を表す出来事の型です。ボタンが押されると Msg の値がランタイムへ渡り、update がその値と現在の Model から新しい Model を作ります。Msg 自体は状態を持たず、状態を変える権限は update だけが持ちます。
ElmはWebAssemblyに出力できますか?
できません。elm make のヘルプは「ElmのコードをJSまたはHTMLへコンパイルする」と説明しており、出力先はこの2種類だけです。0.19.2のリリースノートにも、作者が示した1.0へ向けた計画にも、WebAssembly対応は含まれていません。Wasm出力を前提にするなら別の言語を選ぶことになります。
日本語で読める公式ドキュメントはありますか?
公式ガイド「An Introduction to Elm」の日本語訳が guide.elm-lang.jp で公開されており、検索でも上位に出ます。ただし翻訳の基準は0.19.1時点で、0.19.2で変わったツール周りの記述(配布バイナリの名称やLinux ARM版の追加)は反映されていません。言語仕様は0.19.2でも変わっていないため文法の説明はそのまま使えますが、バージョン固有の情報は elm-lang.org の英語版とGitHubのリリースノートで確認してください。