Gleam v1.12.0 での改善点:依存関係の管理、コードサイズ削減、エラーメッセージ etc.【翻訳】

に公開

この記事は、Gleam公式サイト記事を和訳[1]したものです。

本記事の内容は、Gleam v1.12.0での変更点・追加機能について書かれたものです。Gleamの最新版は以下になります:
https://github.com/gleam-lang/gleam/releases

依存関係管理の悩みにさよなら

By Louis Pilfold (2025/08/05)

GleamはErlangとJavaScriptランタイム向けの型安全でスケーラブルな言語である。本日、Gleam v1.12.0がリリースされた。ここでは目玉となる変更点を紹介する。

依存関係の競合を把握

Gleamパッケージはセマンティックバージョニングを採用しており、互換性のあるリリース範囲を指定することで、選択したすべてのバージョンでの相互互換性を保証している。これにより、プロジェクト全体のコンパイル失敗を引き起こしたり、より分かりにくく微妙な形で不具合を生じさせるようなバージョンを誤って使用してしまう事態を防ぐことができる。

しかし、これは時に依存関係を新しく追加する際に競合が発生しうることを意味する。既にロックした既存のバージョンと互換性のあるバージョンが存在しない場合があるからだ。従来、この問題が発生した際には、有益な情報をほとんど表示することができず、混乱の元となっていた。

error: Dependency resolution failed(依存関係解決の失敗)

An error occurred while determining what dependency packages and
versions should be downloaded.
The error from the version resolver library was:
(ダウンロードする依存パッケージとそのバージョンを特定する際にエラーが発生しました。
バージョン解決ライブラリによるエラーは以下の通りです:)

Unable to find compatible versions for the version constraints in your
gleam.toml. The conflicting packages are:
(gleam.tomlの互換性制約を満たすバージョンが見つかりませんでした。
競合しているパッケージは以下の通りです:)

- app
- wisp
- mist
- gleam_otp
- gleam_json

ここでは何が問題なのか、またその問題を解決するためにプログラマーは具体的に何をするべきなのかが全く明確になっておらず、嬉しくない状況である。

Jak氏の多大な努力と、基盤となるバージョン解決アルゴリズムライブラリpubgrubの新しいリリースにより、今後Gleamはより明確なエラーメッセージを提供できるようになる。

error: Dependency resolution failed(依存関係解決の失敗)

There's no compatible version of `gleam_otp`:
  - You require wisp >= 1.0.0 and < 2.0.0
    - wisp requires mist >= 1.2.0 and < 5.0.0
    - mist requires gleam_otp >= 0.9.0 and < 1.0.0
  - You require lustre >= 5.2.1 and < 6.0.0
    - lustre requires gleam_otp >= 1.0.0 and < 2.0.0
(`gleam_otp`に互換性のあるバージョンが存在しません:
  - wisp >= 1.0.0 且つ < 2.0.0 が必要です
    - wisp には mist >= 1.2.0 且つ < 5.0.0 が必要です
    - mist には gleam_otp >= 0.9.0 且つ < 1.0.0 が必要です
  - lustre >= 5.2.1 且つ < 6.0.0 が必要です
    - lustre には gleam_otp >= 1.0.0 且つ < 2.0.0 が必要です)

There's no compatible version of `gleam_json`:
  - You require wisp >= 1.0.0 and < 2.0.0
    - wisp requires gleam_json >= 3.0.0 and < 4.0.0
  - You require gleam_json >= 2.3.0 and < 3.0.0
(`gleam_json`に互換性のあるバージョンが存在しません:
  - wisp >= 1.0.0 且つ < 2.0.0 が必要です
    - wisp には gleam_json >= 3.0.0 且つ < 4.0.0 が必要です
  - gleam_json >= 2.3.0 且つ < 3.0.0 が必要です)

バージョン解決は(おそらく思った以上に)難しい問題であり、これは些末な改善ではない。Giacomo Cavalieri氏およびpubgrubライブラリのメンテナに心からの感謝を表する。

メジャーバージョン更新の通知について

依存関係管理におけるもう一つの難題は、依存パッケージのいずれかで新しいメジャーバージョンがリリースされた際にそれを検出することである。Gleamにおける依存パッケージのバージョンアップでは、一般にマイナーバージョンやパッチバージョンの違いは許容されても、メジャーバージョンが違うとそうはいかない。セマンティックバージョニング (semver) の性質上、メジャーバージョンの更新は破壊的変更を伴い、互換性を損なう可能性がある。

[dependencies]
lustre = ">= 5.2.0 and < 6.0.0"

依存ライブラリの新しいメジャーバージョンがリリースされると、開発者はそのパッケージの互換性を確認し、必要に応じて現在のコードを修正する場合が生じる。

gleam updateおよびgleam deps downloadコマンドは、利用可能な新しいメジャーバージョンが存在する場合にそのことをメッセージで知らせるようになり、確認・修正作業が必要なタイミングをより簡単に把握できるようになった。

$ gleam update
  Resolving versions(バージョンを解決しています)

The following dependencies have new major versions available:
(以下の依存ライブラリに、利用可能な新しいメジャーバージョンが存在します:)

gleam_http 1.7.0 -> 4.0.0
gleam_json 1.0.1 -> 3.0.1
lustre     3.1.4 -> 5.1.1

この機能の実装に尽力したAmjad Mohamed氏に感謝!

依存関係管理に関する今後の改善

依存パッケージは、プログラミングの生産性を高める上で極めて有用なものである。しかし、単一のプロジェクト内でも、広範なエコシステムに跨る開発でも、これらには注意を払い慎重に扱う必要がある。私たちはパッケージの利用体験を快適で有益なものにしたいと考えており、開発者が「依存関係地獄」 (dependency hell) に陥るのを防ぐことができればと思っている。今後のリリースでは、依存関係の管理と検査、バージョン競合の解決、そしてパッケージマネージャーを通じて利用できるコードが可能な限り高品質で本番環境に適したものであることを保証する新しい機能を提供していく予定である。

echoメッセージ

Gleamには、プリントデバッグに便利なechoキーワードがある。式の直前にこのキーワードを置くことで、その値とソースコード上の位置をコンソールに出力できる。echoは、プリントした値をそのまま返すため、コードの意味を変えることなく任意の位置に挿入することができる。デバッグが終わった後、モジュールから全てのechoキーワードを削除できる言語サーバー用のコードアクションも用意されている。

今回のリリースでは、echoでの出力にカスタムメッセージを追加できるようになり、デバッグの助けとなる付加的な情報を表示できるようになった。

pub fn main() {
  echo 11 as "lucky number"
}
/src/module.gleam:2 lucky number
11

echoによるErlangの文字列リストやJavaScriptの循環参照/エラーインスタンスの表示方法も改善されている。これは、ErlangやJavaScriptの資産を扱うユーザーにとってかなり有用であろう。

Giacomo Cavalieri氏に感謝!

コードサイズの削減

我々は、Gleamのコード生成パイプラインにいくつもの改善を施し、最終的に出力されるコードサイズを削減することができた。これは、ウェブブラウザ上で使用するためJavaScriptにコンパイルするような場合に、特にインパクトが大きい。最初にJavaScriptのプログラムをダウンロードするコストがかかるためで、特に低消費電力デバイスやネットワーク回線が弱い環境において、その効果はより大きなものとなる。

レコード更新の構文のために生成されたコードは、既存の変数を可能な限り再利用するようになった。これに伴い、ErlangとJavaScriptの両方で、生成されるコードのサイズ、定義される変数の数、新しく導入されるスコープの数が削減される。以下に例を挙げる:

pub fn main() -> Nil {
  let trainer = Trainer(name: "Ash", badges: 0)
  battle(Trainer(..trainer, badges: 1))
}

上記のGleamのコードがErlang VM向けにコンパイルされた際に、以前までは次のようなコードを生成していた:

-spec main() -> nil.
main() ->
    Trainer = {trainer, 0, <<"Ash"/utf8>>},
    battle(
        begin
            _record = Trainer,
            {trainer, 1, erlang:element(3, _record)}
        end
    ).

v1.12で導入された変数の再利用により、現在は以下のようなコードが生成される:

-spec main() -> nil.
main() ->
    Trainer = {trainer, 0, <<"Ash"/utf8>>},
    battle({trainer, 1, erlang:element(3, Trainer)}).

これらに加え、case式から生成されるJavaScriptターゲット向けのコードも、多くの場合にサイズが削減される。また、以前のリリースで強化されたデッドコード検出機能の適用範囲が拡大され、最終的なプログラムで使用されないコードが生成されないようになっている。

これらの改善は、Surya氏と私の共同作業によるものである。Surya Rose氏に感謝!

より柔軟なリストのフォーマットが可能に

Gleamには、自動コードフォーマッター (formatter) が搭載されており、Gleam言語サーバーを介してエディタから、あるいはコマンドラインでgleam formatを実行することで利用できる。公式のフォーマッターが存在することで、すべてのGleamコードが予測可能なスタイルに統一され、読みやすくなるとともに、ユーザーは表面的なコードスタイルに関する議論で時間を浪費せずに済むようになる。

従来Gleamのフォーマッターでは、リストがどうフォーマットされるかについてプログラマーは関与できず、要素が一行当たりの上限に収まる場合には常に単一行に配置され、収まらない場合は複数行に分割される仕様であった。今回のリリースで、プログラマーはリストのフォーマットを制御できるようになる。リストが一行に収まる場合でも、複数行に分割するような整形方法を選べるようになったのである。

pub fn my_favourite_pokemon() -> List(String) {
  ["natu", "chimecho", "milotic"]
}

カンマを閉じ角括弧]の前に置くことで、フォーマッターはこのリストを複数行に分割する形で整形する。これにより、フォーマット結果は以下のようになる。

pub fn my_favourite_pokemon() -> List(String) {
  [
    "natu",
    "chimecho",
    "milotic",
  ]
}

リスト内末尾のカンマを削除すると、フォーマッターは再び単一行に収まるよう整形を行うようになる。

新しいフォーマッターでは、要素を1行に1つずつ配置するか、複数の要素を1行に詰め込むかについても、プログラマーが制御できる。また、リスト内の単一の空行は保持されるため、大きなリストを視覚的に異なるパートに分割することで読みやすさを保ったまま整形するのにも便利である。

Giacomo Cavalieri氏に感謝!この変更は、好評を得ること請け合いである。

JSDocのサポート

JSDocは、JavaScriptにおいて最も普及しているコード内ドキュメント記述用のフォーマットであり、多くのエディタやプログラミング用ツールによりサポートされている。JavaScriptにコンパイルされる際、Gleamコード中のドキュメントコメント (documentation comments) は、JSDocの構文に従い出力に反映されるようになる。

Giacomo Cavalieri氏に感謝!

到達不能なインポートへの警告

コードの可読性を低下させるため通常はあまり行われないが、モジュール名を省略した形で関数をインポートすることができる。つまり、インポート先のモジュールで、プリフィクス(module.)なしに関数を使用できるということである。

もし関数や定数が、無修飾で使える状態でインポートされ、かつインポート先において同じ名前の関数や定数が定義されていた場合、インポートされた値には一切アクセスできなくなり、インポート自体が無意味なものとなる。こうしたケースでコンパイラが警告を出すようになったことで、プログラマーは早い段階で問題に気づき修正できるようになった。

Aayush Tripathi氏に感謝!

破棄された変数に関するヒント

コンパイラは、アンダースコア_始まりの既に破棄された変数を、未知の変数が参照しようとしている可能性を検知できるようになった。これにより、該当する変数を強調表示した有益なエラーメッセージが表示されるようになる。

pub fn go() {
  let _x = 1
  x + 1
}

この例では、次のようなエラーが表示される。

error: Unknown variable(未知の変数)
  ┌─ /src/one/two.gleam:4:33let _x = 1-- This value is discarded(この値は破棄されます)
4 │   x + 1
  │   ^ So it is not in scope here.(よって、これはスコープ内に存在しません)

Hint: Change `_x` to `x` or reference another variable
(ヒント:`_x`を`x`に変更するか、他の変数を参照してください)

新しいエラーメッセージは、警告やエラーにおける副次ラベルの表示という面でも改善されており、エラーメッセージの詳細な文脈が提供されるようになっている。副次ラベルは、それと分かるように他とは区別された形で示され、言語サーバーのクライアントでは、診断に付随した追加情報として表示される。

この機能に貢献してくれたGiacomo Cavalieri氏に感謝!

オブジェクトのための不要なアロケーションの回避

パターンマッチング時に、パターンと右側の式が完全に一致するケース節が生じることは珍しくない。次に示すコードのError節のように、値が変更なしにそのまま右側へと渡されるような場合である。

pub fn find_book() -> Result(Book, LibraryError) {
  case ask_for_isbn() {
    Error(error) -> Error(error)
    Ok(isbn) -> load_book(isbn)
  }
}

以前までは、生成されたコードにおいて、マッチしたエラー値を含む新しいErrorオブジェクトが作成されていた。今回のリリースで、コンパイラは最終的に使われる値とパターンに使われている値が同一のものであることを認識するようになり、元の値を再利用するようになった。Gleamはイミュータブル (immutable) な言語なので、こうした最適化は安全に行える。

Giacomo Cavalieri氏に感謝!

冗長な比較への警告

コンパイラは、常に成立あるいは不成立となるような冗長な比較が行われた場合に、警告を発するようになった。例えば、次のような場合である。

pub fn find_line(lines) {
  list.find(lines, fn(x) { x == x })
}

このコードに対して、以下のような警告が発せられる。

warning: Redundant comparison(冗長な比較)
  ┌─ /src/warning.gleam:2:171 │   list.find(lines, fn(x) { x == x })
  │                            ^^^^^^ This is always `True`(常に`True`になります)

This comparison is redundant since it always succeeds.
(この比較は常に成立することが明らかであり、冗長です。)

Giacomo Cavalieri氏に感謝!

Pythonユーザー向けのimportエラーメッセージ

新しい言語を学ぶ際、慣れ親しんだ言語と似ているけれど微妙に異なる構文において、特にミスを犯しがちである。

Gleamにおけるモジュールのインポート構文はPythonのものと似ているが、モジュール名の区切りとして.ではなく/が使われる。これを間違えると、コンパイラは分かりやすいメッセージと共にユーザーにそのことを知らせる。

error: Syntax error(文法エラー)
  ┌─ /src/parse/error.gleam:1:111import one.two.three
  │           ^ I was expecting either `/` or `.{` here.(ここでは、`/`か`.{`が期待されます)

Perhaps you meant one of:
(もしかして:)
    import one/two
    import one.{two}

Zij-IT氏に感謝!

JavaScriptユーザー向けのリスト操作に関するエラーメッセージ

新しいプログラミング言語を学ぶプログラマーは、慣れ親しんだ言語にあっても新しい言語にはない機能を使おうとすることがある。

Gleamには、リストの先頭に要素を追加するための構文が用意されている。これはJavaScriptにおける配列のスプレッド構文に似ているが、新しい配列を作成して全要素をコピーする必要がないため処理速度が速い。JavaScriptの機能と大きく異なるのは、Gleamの場合先頭にしか要素を追加できないという点である。これは、JavaScriptユーザーにとっては予想外となるかもしれない。今回のアップデートにより、スプレッド構文を使って2つのリストを結合しようとした際に、適切なエラーメッセージが表示されるようになった。

error: Syntax error(構文エラー)
  ┌─ /src/parse/error.gleam:5:135[1, ..xs, ..ys]--    ^^ I wasn't expecting a second list here(ここに2つ目のリストが来ることは想定されていません)
  │       │
  │       You're using a list here(既にここでリストが使われています)

Lists are immutable and singly-linked, so to join two or more lists
all the elements of the lists would need to be copied into a new list.
This would be slow, so there is no built-in syntax for it.
(Listはイミュータブルな単方向連結リストとして実装されています。そのため、2つ以上のリストを結合する場合、
全ての要素を新しいリストにコピーする必要があるでしょう。この処理は非常に遅くなる可能性があるため、
リスト同士を結合する組み込み構文は用意されていません。)

Carl Bordum Hansen氏とGiacomo Cavalieri氏に感謝!

関数エラーにおける引数のラベルに関するヒント

関数に渡された引数の数が正しくない場合に出力されるエラーメッセージが改善され、不足している引数のラベル情報が表示されるようになった。

error: Incorrect arity(引数の個数が不正)
  ┌─ /src/main.gleam:6:36 │   Pokemon(198, name: "murkrow")
  │   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Expected 3 arguments, got 2(期待される引数は3個ですが、実際には2個です。)

This call accepts these additional labelled arguments:
(この関数は、他に以下のラベル付き引数を受け取ります:)

  - moves

Giacomo Cavalieri氏に感謝!

Bit arrayの改善

Gleamには、Erlang VM上で動作する言語ではおなじみの、バイナリデータの構築やパターンマッチングのための表現力豊かなリテラル構文がある。この構文は、JavaScriptにコンパイルする際にも利用できる。

新しい機能として、bit arrayでUTFコードポイントを扱う際、要素のエンディアン指定ができるようになった。

また、but arrayパターンのサイズ指定部分に計算式が書けるようになった。例えば、以下のコードのコンパイルが通るようになる。

let assert <<size, data:bytes-size(size / 8 - 1)>> = some_bit_array

Surya Rose氏に感謝!

Erlangにおけるローカル関数のインライン化

Erlang VM向けのコンパイルでは、GleamのコードはビルドツールによりErlangコンパイラを介してBEAMバイトコードに変換される。そのため、GleamはErlangコンパイラが提供するあらゆる最適化の恩恵を受けることができる。

デフォルトで無効になっているオプションの一つに、同一モジュール内から呼び出される関数のインライン化がある。今回のリリースではErlangコンパイラによるインライン化を有効にしている。保守的に設定してあるため、これにより生成されるコードのサイズが増加することはないはずである。この変更のおかげで、一部のBEAMプログラムにおいて、顕著なパフォーマンスの向上が期待できることだろう。

Giacomo Cavalieri氏に感謝!

コードへのリンク機能をモノレポ環境でもサポート

Gleamでは、パッケージがHex (BEAMエコシステム向けパッケージマネージャ) に公開される際、自動生成されたHTML形式のドキュメントも合わせて公開される。このドキュメント内には、型定義や関数のコードへのリンクが埋め込まれており、ドキュメントに目を通してから実際のコード内容を確認できるようになっている。

GleamではリリースごとにGitタグが追加されることを想定しており、このことは前述の機能を実現する上でも役立っている。しかし、タグ名が衝突してしまうことから、複数のGleamプロジェクトを含むモノレポ環境ではうまく動かないという問題があった。

今回のアップデートで、gleam.tomlファイルのrepositoryセクションにtag-prefixを指定できるようになった。これがデフォルトのタグ名の直前に付与されることで、モノレポ内の異なるパッケージ間でタグ名が衝突するのを防げるようになった。

Sakari Bergen氏に感謝!

CommonJSモジュールのサポート

Gleam用パッケージは、Gleamが持つ「外部関数」FFI機能により、JavaScriptやErlangのモジュールを包摂することができる。今回のリリースで、.cjs拡張子を持つJavaScriptモジュールファイルを直接扱えるようになり、CommonJSのインポートシステムが利用できるようになった。

yoshi氏に感謝!

ブロック削除のコードアクション

言語サーバーに、単一の式のみを含むブロックを削除するコードアクションが新たに追加された。次のコードを見てほしい。

case greeting {
  User(name:) -> { "Hello, " <> name }
  //             ^^^^^^^^^^^^^^^^^^^^^ Triggering the code action
  //                                   with the cursor over this block.
  //                                  (カーソルをこのブロック上に置いて、コードアクションを実行します)
  Anonymous -> "Hello, stranger!"
}

上記のコードは以下のように変換される。

case greeting {
  User(name:) -> "Hello, " <> name
  Anonymous -> "Hello, stranger!"
}

Giacomo Cavalieri氏に感謝!

その他

バグ修正とユーザー体験の向上に貢献してくれた以下の面々に感謝の意を表する:
Aayush Tripathi氏、cysabi氏、Giacomo Cavalieri氏、Louis Pilfold氏、そしてSurya Rose氏。

変更点の詳細についてはこちら

支援のお願い

Gleamは企業が所有しているのではなく、スポンサーの支援によって成り立っている。ほとんどのスポンサーが月$5~$20USドルの寄付を行っており、Gleamは私の唯一の収入源となっている。

コアチームメンバーに適正な報酬を支払うという目標に向けて大きな前進はあるものの、まだ道半ばである。GitHub Sponsorsを通じて、プロジェクトやコアチームメンバーであるGiacomo Cavalieri氏とSurya Rose氏への支援を検討していただければと思う。


脚注
  1. 和訳に当たり、PLaMo翻訳を援用していますが、最終的には全て私 (Hizuru) が推敲しています。 ↩︎

Discussion