原文:https://elixir-lang.org/blog/2025/10/16/elixir-v1-19-0-released/
はじめに
この記事は原文の英語の記事を私rickyが個人的に翻訳したものになります。
読みやすさを重視するために英語翻訳をそのままでなく、表現を工夫しています。
そのため内容については時に齟齬やズレが生じる可能性もあります。
当記事を読んでから原文の執筆者へ質問される際は原文を読み込んでから行ってください。
その際に発生するトラブルなどについては責任を負いませんのでご了承ください。
要点
リリースされたelixirv1.19についての開発者であるJosé Valimの説明内容。
改善された型システムや型チェック機能についてかいつまんで説明している。
既存のElixirから改善された機能についての解説が主になっている。
本文
Elixir v1.19では型システムとコンパイル時間のさらなる改善が行われ、より多くのバグをより迅速に発見できるようになりました。
型システムの改善
本リリースでは、無名関数の型推論とプロトコルの型チェックを追加し、型システムを改善しました。これらの改善は一見シンプルに見えますが、現行の理論を拡張し新たな技術を開発することで、既知の知見を凌駕する対応を求められました。技術的な詳細は別の機会に概説します。まずは新機能を見ていきましょう。
プロトコルディスパッチと実装の型チェック
本リリースでは、プロトコルのディスパッチおよび実装時に型チェックを追加しました。
例えば、Elixirの文字列補間ではString.Charsプロトコルを使用します。当該プロトコルへ実装していない値を渡した場合、Elixirはそれに応じた警告を出力します。
以下は、文字列に変換できない範囲を文字列補間に指定する例です:
defmodule Example do
def my_code(first..last//step = range) do
"hello #{range}"
end
end
上記は次の警告を出力します:
warning: incompatible value given to string interpolation:
data
it has type:
%Range{first: term(), last: term(), step: term()}
but expected a type that implements the String.Chars protocol, it must be one of:
dynamic(
%Date{} or %DateTime{} or %NaiveDateTime{} or %Time{} or %URI{} or %Version{} or
%Version.Requirement{}
) or atom() or binary() or float() or integer() or list(term())
また、for内包表記のジェネレータとして、Enumerableプロトコルを実装していないデータ型を渡した場合にも警告が出力されます。
defmodule Example do
def my_code(%Date{} = date) do
for(x <- date, do: x)
end
end
出力:
warning: incompatible value given to for-comprehension:
x <- date
it has type:
%Date{year: term(), month: term(), day: term(), calendar: term()}
but expected a type that implements the Enumerable protocol, it must be one of:
dynamic(
%Date.Range{} or %File.Stream{} or %GenEvent.Stream{} or %HashDict{} or %HashSet{} or
%IO.Stream{} or %MapSet{} or %Range{} or %Stream{}
) or fun() or list(term()) or non_struct_map()
無名関数の型チェックと型推論
Elixir v1.19 では、無名関数の型推論と型チェックが可能になりました。以下に簡単な例を示します:
defmodule Example do
def run do
fun = fn %{} -> :map end
fun.("hello")
end
end
上記の例では明らかな型違反が発生しています。匿名関数はマップを期待しているのに文字列が渡されているためです。Elixir v1.19では、以下の警告が表示されるようになりました:
warning: incompatible types given on function application:
fun.("hello")
given types:
binary()
but function has type:
(dynamic(map()) -> :map)
typing violation found at:
│
6 │ fun.("hello")
│ ~
│
└─ mod.exs:6:8: Example.run/0
関数キャプチャ(例: &String.to_integer/1)もElixir v1.19から型を伝播するようになり、Elixirの型システムがプログラム内のバグを検出する機会が増えます。
謝辞
本型システムはCNRSとRemoteの協力により実現しました。開発作業は現在、Fresha、Starfish*、Dashbitによって支援されています。
大規模プロジェクトにおけるコンパイル時間の短縮
本リリースでは、大規模コードベースにおいて最大4倍のビルド高速化を実現する2つのコンパイラ改善が含まれています。
Elixirは従来からプロジェクトや依存関係内の指定ファイルを並列コンパイルしてきましたが、コンパイラがマシンリソースを効率的に活用できない場合がありました。本リリースでは2つの一般的な制限に対処し、コードベースの規模と利用可能なCPUコア数に応じてパフォーマンス向上が得られるよう改善しています。
コード読み込みのボトルネック解消
本リリース以前は、Elixirはモジュールが定義されるとすぐに読み込んでいました。しかし、コード読み込みのErlang部分は単一プロセス(コードサーバー)内で実行されるため、これがボトルネックとなり、特に大規模プロジェクトでは並列化が阻害されていました。
本リリースではモジュールを遅延ロードする方式を採用。これによりコードサーバーへの負荷とコンパイル時の処理量が軽減され、大規模プロジェクトではコンパイル速度が2倍以上高速化される報告があります。効果はコードベースの規模と利用可能なCPUコア数に依存します。
実装面では、並列コンパイラは既にコンパイル時のモジュール解決メカニズムとして機能しているため、これを基盤としました。コンパイラがモジュールコンパイルとモジュールロードの両方を制御することで、決定論的なビルドをより確実に保証できます。
このアプローチには二つの潜在的な後退点があります。一つ目は、コンパイル中にプロセスを生成し、同じプロジェクト内で定義された他のモジュールを呼び出す場合です。
例えば:
defmodule MyLib.SomeModule do
list = [...]
Task.async_stream(list, fn item ->
MyLib.SomeOtherModule.do_something(item)
end)
end
生成されたプロセスはコンパイラから認識されないため、MyLib.SomeOtherModuleをロードできません。以下のいずれかの方法で対応してください:Kernel.ParallelCompiler.pmap/2を使用するか、該当モジュールを使用するプロセスを生成する前に明示的にCode.ensure_compiled!(MyLib.SomeOtherModule)を呼び出します。
2つ目のケースは、同じプロジェクト内で定義された他のモジュールを呼び出す@on_loadコールバック(主にNIFsで使用)に関連します。例:
defmodule MyLib.SomeModule do
@on_load :init
def init do
MyLib.AnotherModule.do_something()
end
def something_else do
...
end
end
MyLib.SomeModule.something_else()
これが失敗する理由は、@on_loadコールバックがコードサーバー内で呼び出されるため、追加モジュールの読み込み能力が制限されるからです。@on_loadコールバック中の外部モジュール呼び出しは一般的に制限することが推奨されますが、どうしても必要な場合は、呼び出されるモジュール内で@compile {:autoload, true}を設定することで、前方互換性と後方互換性を保った方法でこの問題を解決できます。
上記のコードスニペットは過去において非決定的なコンパイル失敗を引き起こす可能性がありましたが、今回の変更により、これらのケースのコンパイルは決定的になりました。
依存関係の並列コンパイル
本リリースでは、MIX_OS_DEPS_COMPILE_PARTITION_COUNT という変数が導入され、mix deps.compile に依存関係を並列でコンパイルするよう指示します。
依存関係の取得や個々のElixir依存関係のコンパイルは既に並列化されていましたが(前節で説明)、ネイティブコードを含む依存関係や、1~2つの大規模ファイルがコンパイル時間の大半を占める依存関係など、性能向上が十分に発揮されない病的なケースが存在しました。
MIX_OS_DEPS_COMPILE_PARTITION_COUNT を 1 より大きい数値に設定すると、Mix は複数の依存関係を同時にコンパイルし、別々の OS プロセスを使用します。実証テストでは、マシン上のコア数の半分に設定するだけでリソース使用率を最大化できることが示されています。正確な速度向上は、依存関係の数と n に依存します。
Erlang/OTP 28のサポート
Elixir v1.19はErlang/OTP 28.1以降を正式サポートします。正規表現の新しいErlang/OTP 28表現をサポートするため、構造体は__escape__/1コールバックを定義することで、抽象構文木へのエスケープ方法を制御できるようになりました。
一方、Erlang/OTP 28+における正規表現の新しい表現は、構造体フィールドのデフォルト値として使用できなくなることを意味します。したがって、以下のような記述は許可されません:
defmodule Foo do
defstruct regex: ~r/foo/
end
ただし、構造体自体を初期化する際には正規表現を使用できます:
defmodule Foo do
defstruct [:regex]
def new do
%Foo{regex: ~r/foo/}
end
end
OpenChain認証
Elixir v1.19は、以前発表した通り、OpenChain準拠後の初リリースとなります。要約すると:
- Elixirリリースには、CycloneDX 1.6以降およびSPDX 2.3以降のフォーマットによるソースSBoMが含まれるようになりました。
- 各リリースはソースSBoMと共に認証されます。
これらの追加により、各リリースのコンポーネントとライセンスに関する透明性が向上し、より厳格なサプライチェーン要件をサポートします。
本作業はJonatan Männchen氏により実施され、Erlang Ecosystem Foundationの支援を受けています。
概要
今回のリリースには他にも多くの改良点があります。例えば、オプション解析の改善、ExUnitのデバッグ性とパフォーマンスの向上、mix help Mod、mix help Mod.fun、mix help Mod.fun/arity、mix help app:packageの追加により、シェル経由で人間やエージェントがドキュメントにアクセス可能になったことなどです。完全なリリースノートはCHANGELOGをご覧ください。
コーディングを楽しんでください!