10月17日、Elixir公式ブログが「Elixir v1.19 released: enhanced type checking, broader type inference, and up to 4x faster compilation for large projects」と題した記事を公開した。この記事では、型推論と型検査の強化、プロトコルや無名関数に対する検査の拡張、そして大規模プロジェクトで最大4倍のビルド高速化につながるコンパイラ改良について詳しく紹介されている。以下に、その内容を紹介する。
型システムの強化:モジュール単位の型推論と広範な型検査
Elixir 1.19は、ガードを除くあらゆる構文に対して型推論を実施し、同一モジュール内の関数呼び出しや標準ライブラリのシグネチャを考慮して関数の型を推定するようになった。これにより、明示的なガードがない関数でも、実際の利用から適切な型が導かれる。たとえば次の2つの even?/1
は、いずれも integer() -> boolean()
と推論される。
def even?(x) when is_integer(x) do
rem(x, 2) == 0
end
def even?(x) do
rem(x, 2) == 0
end
型推論にはいくつかのトレードオフが存在するため、Elixirはモジュール型推論という折衷的アプローチを採用している。記事では、次の観点が整理されている。
- 速度 — 一般に型推論は型検査より計算コストが高い。
- 表現力 — 推論できる構文は型検査できる構文の真部分集合であり、完全推論は言語の表現力を制限しうる。
- 増分コンパイル — 依存関係の変更が推論のやり直しを誘発しがちで、大規模プロジェクトでは安定性のための明示シグネチャ追加が有効となる場合がある。
- エラーの連鎖 — 型矛盾があると、推論の過程でエラーメッセージが不明瞭になる恐れがある。
将来リリースでは、ガード式の型推論や依存パッケージの型シグネチャを考慮した推論の導入が計画されているという。明示シグネチャを与えた関数については、他の静的型付き言語と同様に、推論ではなくユーザ指定の型に対する検査が優先される。
プロトコルのディスパッチと実装の型検査
Elixir 1.19は、プロトコルのディスパッチおよび実装に対する型検査を導入した。String.Chars
を実装しない値を文字列補間に渡した場合などに、より適切な警告が得られる。以下は Range
を補間しようとして警告が出る例である。
defmodule Example do
def my_code(first..last//step = range) do
"hello #{range}"
end
end
Enumerable
を実装しない値をfor内包表記のジェネレータに渡した場合にも警告が出る。
defmodule Example do
def my_code(%Date{} = date) do
for x <- date, do: x
end
end
無名関数の型推論と型検査
無名関数(fn
)についても型推論と検査が行われるようになった。以下の例では、マップを受け取る無名関数に文字列を渡しており、コンパイル時に型不一致の警告が出る。
defmodule Example do
def run do
fun = fn %{} -> :map end
fun.("hello")
end
end
加えて、&String.to_integer/1
のような関数キャプチャも型が伝搬し、バグ検出の機会が広がる。
コンパイル高速化:遅延ロードと依存の並列ビルド
大規模コードベースで最大4倍のビルド高速化が報告される背景には、2つのコンパイラ改善がある。第一に、モジュールの遅延ロードにより、Erlang側の「コードサーバ」単一プロセスがボトルネックになる問題を緩和し、並列化効率を高めた。第二に、新しい環境変数 MIX_OS_DEPS_COMPILE_PARTITION_COUNT
により、mix deps.compile
が依存を複数のOSプロセスで並列コンパイルできるようになった。CIで有効化する場合はメモリ使用量が増える点に留意が必要で、経験的には物理コア数の半分に設定すると資源利用が最大化しやすいという。
なお、遅延ロードに伴い、コンパイル時にプロセスを生成して同一プロジェクト内の別モジュールを呼び出すケースや、**@on_load
コールバックで同一プロジェクト内のモジュールを呼び出すケースは挙動に注意が必要である。前者には Kernel.ParallelCompiler.pmap/2
の利用か Code.ensure_compiled!/1
の明示呼び出し、後者には呼び出される側で @compile {:autoload, true}
を付与する回避策が提示されている。これらの変更により、過去に非決定的なコンパイル失敗を引き起こし得たケースも決定的**にコンパイルできるようになった。
Erlang/OTP 28 サポートと正規表現の扱い
Elixir 1.19は Erlang/OTP 28.1+ を公式サポートする。OTP 28における正規表現の内部表現の変更に合わせ、構造体(struct)が抽象構文木(AST)へのエスケープ方法を制御できる __escape__/1
を導入した。一方で、構造体フィールドのデフォルト値として正規表現を置くことは不可となり、次のような定義はエラーになる。構造体の初期化時に値として渡す形であれば引き続き利用できる。
# 許可されない
defmodule Foo do
defstruct regex: ~r/foo/
end
# 代替例
defmodule Foo do
defstruct [:regex]
def new do
%Foo{regex: ~r/foo/}
end
end
注釈:Erlang/OTPとは?
Elixirは、Erlangという関数型言語の仮想マシン(BEAM)上で動作している。
OTP(Open Telecom Platform)はErlangの標準ライブラリ群を指し、並行処理・監視・分散処理などの機能を提供する。
「Erlang/OTP 28.1+」は、Elixir 1.19が動作を保証するErlang実行環境のバージョン(28.1以降)を意味する。
Erlang/OTP 28では正規表現の内部表現変更やVMのスケジューラ改善などが行われ、Elixirの並列コンパイル機構とより高い互換性を持つ。
OpenChain準拠とSBOMの提供
本リリースはOpenChain準拠の第一弾でもあり、各リリースにCycloneDX 1.6/SPDX 2.3 形式のSource SBOMが含まれ、アテステーション(attestation)も付与される。
注釈:アテステーションとは?
ソフトウェア開発における「アテステーション」は、特定のビルドや成果物が「誰によって」「どのような手順で」「どのソースコードから」生成されたかを証明する署名付きの証明書(真正性の宣言)のことである。これにより、Elixirのリリースが本当に公式にビルドされたものであり、改ざんされていないことを検証できる。
サプライチェーン攻撃対策の観点からも重要で、SigstoreやSLSA Frameworkなどと並び、近年のソフトウェア供給網の信頼性確保に必須の仕組みとなっている。
これにより、リリースに含まれる依存コンポーネントのライセンスや構成情報を透明にし、より厳格なサプライチェーン要件に対応することが可能となった。
そのほかの改善
そのほか、オプションパースの改善、ExUnit のデバッグ性と性能向上、そしてシェルからドキュメントへ素早くアクセスするための mix help Mod
、mix help Mod.fun
、mix help Mod.fun/arity
、mix help app:package
の追加など、多数の改良が含まれる。詳細はCHANGELOGに整理されている。
詳細はElixir v1.19 released: enhanced type checking, broader type inference, and up to 4x faster compilation for large projectsを参照していただきたい。