Elmは、JavaScriptにコンパイルされるフロントエンド向けの関数型プログラミング言語です。英単語の elm は「ニレの木」を指しますが、ここで扱うのはEvan Czaplicki氏が2012年に公開した言語のほうです。2019年10月の0.19.1から長く更新が止まっていましたが、2026年7月6日に0.19.2、同年10月2日に0.19.3が公開され、1.0へ向けた小刻みなリリースが始まっています。この記事では、0.19.2と0.19.3で何が変わったのか、The Elm Architectureと型システムがどう働くのか、テストと周辺ツールの現状、そしてElmを採用してよい条件と外すべき条件を、公式の一次情報と0.19.3での実行結果に基づいて整理します。
まとめ:Elmの現在地と採用判断の要点
Elmの開発は再開しています。ただし言語仕様は動いていません。2026年10月2日の0.19.3は0.19.2から88日後のリリースで、どちらもリリースノートに「言語の変更はない」と明記されています。中身はコンパイラの性能改善と内部構造の作り直しで、作者は「より速いリリースサイクルを試している」と書いています。2026年11月1日には、版番号を0.19.4へ上げるコミットが開発ブランチに入りました。一方、標準ライブラリの elm/core は2020年2月15日の1.0.5から更新がありません。
採用判断はJavaScript資産への依存度で決まります。地図・チャート・決済SDKなど既存のJSライブラリが画面の中心にあるUIでは、ライブラリを使うたびに ports か Custom Elements を介したデータの受け渡しやUIの埋め込みを実装することになり、その工数が先に立ちます。フォームと画面遷移が中心で、同じチームが5年以上保守する業務系SPAなら、コンパイラが全参照を検査するElmのリファクタリング耐性が効きます。以下、版ごとの変更点、TEAの構造、型システム、テストとツール、JavaScript連携、他の選択肢との線引きの順に見ていきます。
Elm(プログラミング言語)とは:型検査で実行時エラーを防ぐフロントエンド言語
Elmは、ブラウザで動くUIを書くための言語であり、アーキテクチャ・ビルドツール・パッケージ管理を内包した一体型の処理系です。公式サイトはElmを、親切なエラーメッセージ、高い性能、小さなアセット、実行時例外がないことを備えた言語と説明しています。言語そのものが状態管理の型を決めるため、React・状態管理ライブラリ・型チェッカの組み合わせを自分で選ぶ余地はありません。この選べなさが、Elmの長所と短所の両方の出どころです。
パッケージの配布にも特徴があります。package.elm-lang.org は投稿されたパッケージの公開APIを型情報から比較し、破壊的変更があればメジャーバージョンの繰り上げを強制します。版番号が人間の申告でなく型検査の結果で決まるため、elm.json に書いた依存が意図せず壊れる事故が起きにくい仕組みです。また0.19以降、JavaScriptを直接呼ぶ「カーネルコード」を含められるのは elm と elm-explorations の公式パッケージだけで、コミュニティのパッケージはElmだけで書かれています。
Elmは型検査によって多くの実行時エラーを防ぎますが、Elmのコードの内側でも実行時例外が起きる操作は残っています。elm/core 1.0.5 のBasicsのソースは、関数や elm/json のJSON値に == を使うとクラッシュすると明記しています。後述の ports で受け渡した先のJavaScriptが投げる例外は消えません。境界の外は従来どおり自分で守る必要があります。
0.19.2と0.19.3の変更点:6年8か月の空白から3か月刻みのリリースへ
0.19.2:パーサのメモリ確保削減によるビルド高速化
2026年7月6日の0.19.2は、0.19.1(2019年10月21日)から約6年8か月ぶりのリリースで、変更はコンパイラの性能改善に絞られています。作者の解説記事によると、パース時のメモリ確保を減らしたことで、85万行規模のコードでGCのコピー量が20%減、ピークメモリ使用量が10%減、全体で7%速くなりました。85万行のフルビルドは5.7秒、増分ビルドは350ミリ秒未満です。実プロジェクトでの差は「わずかな改善から最大1.9倍」で、上限の例は351モジュールのフルビルドが4.981秒から2.595秒に縮んだケースです。0.19.1に無かったLinuxのARM版バイナリが追加され、コンパイラの既定ブランチも master から main へ切り替わりました。
0.19.3:コンパイラ内部を「構造で正しさを保証する」形へ作り直し
0.19.3もリリースノートは「言語の変更はない」と明記しており、elm.json の “elm-version” を “0.19.3” に書き換えれば既存コードはそのまま通ります。0.19.2との差分は95コミットで、作者の告知記事「Another step towards Elm 1.0」(2026年10月5日)は、その中身を次のように説明しています。
- 作者が開発するAcadiaのコンパイラはElmのコンパイラよりかなり先に進んでおり、その基盤をElmへ戻す作業を数回の小さなリリースに分けて出す
- 型名・モジュール名・変数名・演算子名などを、すべて別々の型として扱うようにした。この作業の途中で、入れ子の拡張レコードを含む型エイリアスの展開で拡張変数が正しく変換されないバグが見つかった
- モジュールの循環参照のエラー表示を、空にならない最小の循環として必ず1本の線で描くように変えた
- 並行処理・バイナリのシリアライズ・ファイルロックの基盤を、「有効なプロジェクトロックを持つときだけファイルを書ける」といった保証を型で強制する形に改めた
利用者の目に見える変化は、エラー表示とヘルプのリンク先がほぼすべてです。0.19.3で3つのモジュールが循環するコードをコンパイルすると、次の出力になりました。
-- IMPORT CYCLE ----------------------------------------------------------------
Your module imports form a cycle:
┌─────┐
│ Blog
│ ↓
│ User
│ ↓
│ Post
└─────┘
Learn more about why this is disallowed and how to break cycles here:
<https://elm-lang.org/0.19.3/import-cycles>
Detected a problem.
後述のカウンターアプリを0.19.3で elm make –optimize した出力は109,951バイト(gzip圧縮後26,518バイト)で、0.19.2で測った109,959バイトとほぼ同じでした。少なくともこのカウンターでは、出力サイズに差はほとんど出ていません。
「オワコン」かどうかの判定材料:リリース間隔と1.0への計画
Elmの将来性を疑う声の理由は、版の日付を並べるとわかります。2026年11月6日時点の状況です。
| 項目 | バージョン | 公開日 |
|---|---|---|
| コンパイラ elm/compiler | 0.19.3 | 2026-10-02 |
| 同 前の版 | 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 |
| elm/json | 1.1.4 | 2025-09-19 |
コンパイラのリポジトリはstar約7,930、未解決のissueが281件、オープンなプルリクエストが26件です。6年以上止まっていたあいだ、作者はデータベース関連のコンパイラであるAcadiaに取り組んでおり、0.19.2の時点で非破壊の小さなリリースを重ねて1.0を出す方針と、equatable・hashable といった型の追加を公表していました。0.19.3の告知では、Acadiaとそのサーバー向けライブラリ elm-simple-server を「Elmの体験をサーバーとデータベースへ広げる取り組み」と位置づけ、AcadiaがElm開発の資金源も兼ねると説明しています。
Elmは開発が終わった言語ではありません。7月と10月の2回のリリースと0.19.4ブランチがその根拠です。一方、言語仕様と標準ライブラリはまだ動いておらず、1.0の時期も示されていません。新規案件では、将来の言語機能追加を前提にせず、現行機能で要件を満たせることと、想定する保守期間にわたって担当者を確保できることを採用条件にします。既存のElmプロジェクトでは、コンパイラを0.19.3に更新し、elm.json の elm-version も変更してビルドを確認できます。elm-testなどの周辺ツールも対応版にそろえます。
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の知識は要らず、2016年より前の解説はそのままでは使えません。
Msg と update の役割はよく混同されます。Msg は「何が起きたか」を表すデータで、それ自体は状態を変えません。ボタンが押されたという事実は Increment という値になってランタイムへ渡り、update だけがその値を受け取って新しいModelを返します。ランタイムが呼ぶ更新処理の入口をupdateに集約し、必要に応じて補助関数へ処理を分けます。Modelは不変の値なので、別の場所から直接書き換えられることはありません。この構造は判別可能なユニオン型を言語機能として持つことで成り立っています。
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 }
Browser.sandboxとBrowser.elementの選び分け
上のコードの Browser.sandbox は、外の世界と通信しないアプリ用の入口です。HTTPリクエストもタイマーもJavaScript連携も使えません。
外部と通信するなら Browser.element を選びます。init と update が ( Model, Cmd Msg ) を返すようになり、副作用を Cmd という値でランタイムへ委ねます。subscriptions で外から届く出来事も購読できます。ページ全体の本文とタイトルを扱うなら Browser.document、URL変更やリンククリックも管理するなら Browser.application を使います。Browser.sandbox で書き始め、通信が要る時点で Browser.element へ移すのが実務の手順です。
型システム:Maybe・Result・カスタム型による値の区別
MaybeとResultによる欠損値・失敗の表現
Elmには null も undefined もありません。値が無いかもしれない場所は Maybe、失敗するかもしれない処理は Result で明示し、case 式で分岐する場合はすべてのケースを扱う必要がありますが、Maybe.withDefaultやResult.mapなどの関数でも処理できます。0.19.3の 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 で返し、それを整数として使おうとした時点でコンパイルを止めます。バグの発見位置がブラウザの実行時からターミナルのビルド時へ移る、というのが型システムの効果です。
カスタム型と非公開コンストラクタによる検証済み値の区別
0.19.3の告知記事が例に挙げているのが、文字列を包んだだけの型で「検証済み」を表す書き方です。Email 型のコンストラクタを公開しなければ、モジュール外からの生成をvalidate経由に限定できます。ただし、この例の検証は「@を含むか」だけで、メールアドレスの形式や到達可能性を保証しません。
module Email exposing (Email, toString, validate)
type Email
= Email String
validate : String -> Maybe Email
validate raw =
if String.contains "@" raw then
Just (Email raw)
else
Nothing
toString : Email -> String
toString (Email raw) =
raw
Email と String は別の型なので、検証前の文字列をメールアドレスとして渡すコードはコンパイルが通りません。告知記事は、コンパイラがこの包みを認識して生成JSでは素の文字列を使うため実行時の負担はないと説明しています。0.19.3で確かめると、これは –optimize を付けたビルドの話でした。–optimize ありでは Email のコンストラクタが恒等関数(identity)になり、付けないビルドでは {$: ‘Email’, a: a} というオブジェクトで包まれます。0.19.3のコンパイラ自身も、型名・モジュール名・変数名を同じ考え方で別の型に分け、その過程で前述のバグを見つけています。税込価格と税抜価格のように、取り違えると困る値が並ぶ業務画面で効く技法です。
コンパイラのエラーメッセージが示す修正手順
Elmのエラーメッセージは型の不一致を報告するだけでなく、修正案まで書きます。整数を文字列に連結しようとしたコードを0.19.3でコンパイルした出力です。
-- TYPE MISMATCH --------------------------------------------------- src/Bad.elm
I thought I was appending String values here, not Int values like this:
6| "count: " ++ n
^
Try using String.fromInt to turn it into a string?
Detected problems in 1 module.
該当行と桁位置、期待した型、使うべき関数名(String.fromInt)まで一度に示されます。Elmが関数型言語の入門に推される理由の実体はここにあります。関数型の考え方そのものは関数型プログラミングの基本と純粋関数・不変性で整理しています。
Elm 0.19.3のインストール:elm init・elm reactor・elm make
導入方法は2通りです。GitHubのリリースページから各OS向けのバイナリ(elm-0.19.3-mac-x64.gz など。macOSとLinuxはARM版もあります)かmacOS/Windows用インストーラを取得するか、Node.jsがある環境なら npm install -g elm を実行します。npmの elm パッケージは2026年10月5日に 0.19.3-0 になりました。展開したバイナリに実行権限を付け、elm –version が 0.19.3 を返せば準備完了です。
プロジェクトの初期化は elm init です。src ディレクトリと elm.json が作られ、ソースは src 配下に拡張子 .elm で置きます。0.19.3が生成した elm.json は次のとおりで、依存パッケージの版は0.19.2のときと同じでした。
{
"type": "application",
"source-directories": [
"src"
],
"elm-version": "0.19.3",
"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 init・elm install・elm bump・elm diff・elm publish を加えた8つが、0.19.3のコマンドのすべてです。標準のelm makeだけでビルドでき、変更監視などが必要ならelm-watchのような周辺ツールを組み合わせられます。
elm-testによるテスト:elm-explorations/testとファズテスト
Elmのコンパイラはテストの実行機能を持ちません。テストはnpmの elm-test(テストランナー)と、Elmパッケージの elm-explorations/test(テストを書くためのライブラリ)を組み合わせて書きます。elm-test は2026年10月5日に 0.19.3-0 が出ており、リリースノートによるとこの版から Elm 0.19.3 が必須です。PATHに elm 0.19.3 がある状態で npm install –save-dev [email protected] を実行し、続けて npx elm-test init を実行すると、elm.json の test-dependencies に elm-explorations/test 2.2.1 が入り、tests ディレクトリが作られました。
前章の Email モジュールに対して、通常のテスト2本とファズテスト1本を書いた例です。
module EmailTest exposing (suite)
import Email
import Expect
import Fuzz
import Test exposing (Test, describe, fuzz, test)
suite : Test
suite =
describe "Email.validate"
[ test "@を含む文字列は通す" <|
\_ ->
Email.validate "[email protected]"
|> Maybe.map Email.toString
|> Expect.equal (Just "[email protected]")
, test "@を含まない文字列は弾く" <|
\_ ->
Email.validate "example.com"
|> Expect.equal Nothing
, fuzz Fuzz.string "@を含まない任意の文字列は Nothing" <|
\s ->
Email.validate (String.replace "@" "" s)
|> Expect.equal Nothing
]
npx elm-test を実行すると「Running 3 tests」に続いて TEST RUN PASSED、Passed: 3 と表示されました。ファズテストは既定で100通りのランダムな文字列を生成し、出力の末尾に –seed の値を出すので、失敗したときは同じ値で再現できます。ランダム入力で性質を確かめる考え方はプロパティベーステストの書き方と縮小・再現の仕組みで詳しく扱っています。純粋関数しかないElmでは、update と view も入力を与えて結果を比べるだけでテストできます。
周辺ツールの版と役割
コンパイラの外側はコミュニティのツールが担っています。2026年11月6日時点で、npmレジストリの latest タグが指す版とその公開日です。
| ツール | 役割 | 最新版 | 公開日 |
|---|---|---|---|
| elm-format | コード整形 | 0.8.8 | 2025-06-19 |
| elm-review | 静的解析 | 2.13.5 | 2025-12-30 |
| @elm-tooling/elm-language-server | エディタ連携 | 2.10.0 | 2026-10-02 |
| elm-watch | 監視ビルド | 1.2.9 | 2026-10-13 |
| elm-pages | SSR・事前生成 | 3.5.1 | 2026-05-12 |
elm-review は、問題を起こすと知られている書き方をソースから検出する静的解析ツールです。ルールはElmパッケージとして配布され、CLIに –fix を付けると、自動修正を持つエラーについて修正案を示し、採否を選べます。フルスタックのElm環境である Lamdera のコンパイラも、npmでは0.19.3に合わせた 0.19.3-1.5.0 が2026年10月27日に出ています。UIを組む側では、CSSを書かずにレイアウトを型で組む mdgriffith/elm-ui が知られていますが、最新の1.1.8は2020年8月2日の公開です。採用前にリポジトリの更新状況を見ておくべき代表例です。
JavaScriptとの連携:portsによるメッセージの送受信
コミュニティのパッケージはJavaScriptを直接呼べないため、アプリとJavaScriptをつなぐ仕組みが別に用意されています。公式ガイドのJavaScript連携の章が挙げるのは、起動時に値を渡す flags、実行中にメッセージを送受信する ports、JavaScript製のウィジェットをHTML要素として埋め込む Custom Elements の3つです。ここでは 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 )
このPortsモジュールを動かすには、Browser.elementのupdateからPorts.updateを呼び、subscriptionsでPorts.received Ports.Receivedを購読します。JavaScript側は app.ports.save.subscribe で値を受け取り、app.ports.received.send で値を送り返します。やり取りは非同期のメッセージで、JavaScriptがElmの内部状態を直接触ることはできません。
代償は工数です。地図やチャートのライブラリを使うたびに、値の送受信とエラー時の扱いを自分で設計することになります。npmのエコシステムを前提にした画面をElmで作り直すなら、この設計コストを先に見積もってください。関数型言語のコードをJavaScriptへ変換するという発想は珍しくなく、Haskell側の同種の取り組みはGHCJSとGHCのJavaScriptバックエンドとして整理しています。
React・TypeScript・GleamとElmの選び分けとElmを外すべき条件
Elmと比較されるのは、同じ問題に別の角度から答えるTypeScript + ReactとGleamです。
| 観点 | Elm | TypeScript + React | Gleam |
|---|---|---|---|
| 型検査の範囲 | 言語全体 | 型注釈・型推論(any等で制約あり) | 言語全体 |
| JS資産の再利用 | ports・Custom Elements等 | そのまま | FFI経由 |
| 状態管理 | 言語が規定(TEA) | ライブラリを選ぶ | フレームワーク次第 |
| 導入の粒度 | DOM要素単位から | ファイル単位 | プロジェクト単位 |
| 主戦場 | ブラウザUI | ブラウザUIと汎用 | BEAM上のサーバー |
既存のReactアプリに型安全性を足したいだけなら、答えはTypeScript 6.0とその移行です。ファイル単位で段階導入でき、npmの資産をそのまま使えます。React 19.2の新機能のように更新が年に何度も入る点も、発注者への説明では有利に働きます。関数型のパラダイムに関心があり、対象がサーバーサイドなら、Erlang VM上で動くGleamが別の答えです。Gleamは2026年10月12日にv1.19.2を出しており、リリースの頻度ではElmより一段速く動いています。
Elmを外すべき条件で最も重いのは、外部SDKへの依存です。地図・帳票・決済などのJSライブラリが画面の中心にあるなら、ports や Custom Elements のラッパー設計が主作業になり、言語の利点を回収できません。次に効くのが体制で、要員を短期で入れ替える前提なら避けるべきです。日本語の情報は公式ガイドの翻訳が中心で、検索上位に並ぶ解説記事も0.19.1以前の記述で止まっています。初期HTMLをサーバーで返したいサイトも、Elm単体では対象外です。elm make が出力できるのはJavaScriptとHTMLだけで、サーバーで描画するルートと事前生成するルートを1つのアプリで併用できる elm-pages のような、コミュニティの仕組みに頼ることになります。
採用が合理的なのは、業務系の管理画面やフォーム中心のSPAで、外部SDKへの依存が薄く、同じチームが数年単位で保守し続ける場合です。仕様変更で型を1か所直せばコンパイラが影響範囲を全部指摘する性質は、テストの薄い長寿命コードほど効きます。こうした業務用Webアプリ開発で言語選定から相談したい場合は、要件と保守体制を先に整理しておくと判断が速くなります。
よくある質問
elmという単語の意味は何ですか?
英語の elm は「ニレの木」を意味する普通名詞です。プログラミング言語のElmはこの単語と同じ綴りですが、命名の理由は elm-lang.org にもコンパイラのリポジトリにも書かれていません。検索結果に樹木の解説と言語の解説が混ざるのはこのためで、言語について調べるなら「elm 言語」「elm lang」のように語を足すと目的の情報に届きます。
Elmの最新バージョンはいくつですか?
2026年11月6日時点の最新は0.19.3で、2026年10月2日に公開されました。手元の版は elm –version で確認できます。0.19.1や0.19.2からの更新では言語仕様の変更はありませんが、コンパイラの更新とelm.json の “elm-version” の変更が必要です。周辺ツールの対応版を確認し、ビルドとテストも実行してください。次の0.19.4は開発ブランチで作業が進んでいる段階です。
ModelとMsgはどう違いますか?
Model はアプリが今持っている状態そのもので、Msg は「何が起きたか」を表す出来事の型です。ボタンが押されると Msg の値がランタイムへ渡り、update がその値と今の Model から新しい Model を作ります。Msgは入力文字列や取得結果などのデータを持てますが、それ自体でModelを更新せず、updateがそのデータを使って次のModelを返します。
ElmはWebAssemblyに出力できますか?
できません。0.19.3の elm make のヘルプは「ElmのコードをJSまたはHTMLへコンパイルする」と説明しており、出力先はこの2種類だけです。0.19.2と0.19.3のリリースノートにも、告知記事のロードマップにも、WebAssembly対応は出てきません。Wasm出力を前提にするなら別の言語を選ぶことになります。
日本語で読める公式ドキュメントはありますか?
公式ガイド「An Introduction to Elm」の日本語訳が guide.elm-lang.jp で公開されています。翻訳の基準は0.19.1時点ですが、0.19.2と0.19.3は言語仕様を変えていないので、文法とTEAの説明はそのまま使えます。配布バイナリの名前やエラー表示など版に依存する情報は、elm-lang.org の英語版とGitHubのリリースノートで確認してください。