2 ポイント 投稿者 GN⁺ 2024-09-06 | 1件のコメント | WhatsAppで共有
  • Clojure 1.12.0 は Java 8 バイトコードを維持しつつ、今後のリリースで最小 Java 互換性とバイトコード基準を新しい Java LTS に移す前の、最後の Java 8 基準リリースになる予定
  • JDK 21 の 仮想スレッド 環境で、lazy-seqdelaysynchronized の代わりにロックを使うようになり、ブロッキング I/O が実スレッドを固定してしまう状況を減らす
  • REPL では JVM を再起動せずに add-libadd-libssync-deps でライブラリを追加できるが、この機能は 開発中の対話的利用 に限定される
  • Java 相互運用性が拡張され、メソッド値:param-tags、配列クラス構文、関数型インターフェース変換、Supplier、Java Stream 処理関数が追加
  • パフォーマンスと互換性の面では、PersistentVector spliterator、効率的な drop / パーティション処理、Var interning ポリシーの強化、CVE-2024-22871 の修正、Java シリアライズ識別子の整理が含まれる

Java 8 互換性、セキュリティ、シリアライズ整理

  • Clojure 1.12.0 のダウンロードと利用情報は Downloads ページで確認できる
  • Java 8 基準 は今回のリリースでも維持される
    • Clojure 1.12 は Clojure 1.10、1.11 と同様に Java 8 バイトコードを生成する
    • 今後のリリースでは、バイトコードと最小 Java 互換性をより新しい Java LTS リリースへ移行する予定
  • JDK 21 の 仮想スレッドのピン留め 問題が緩和された
    • 1.12 より前では、lazy-seqdelay は一度だけ実行されることを保証するため、ユーザーコードを synchronized ブロック内で実行していた
    • JDK 21 時点では synchronized はまだ協調的ブロッキングに参加しないため、そのコードがブロッキング I/O を行うと実スレッドを固定することがある
    • -Djdk.tracePinnedThreads=full を使うと、JDK 21 がこの状況について警告を出す場合がある
    • 1.12 では lazy-seqdelaysynchronized ブロックの代わりにロックを使用する
  • セキュリティ修正として CVE-2024-22871 が取り込まれており、関連する勧告は GHSA-vr64-r9qj-h27f にある
  • Java シリアライズ関連クラスの serialVersionUID が明示的に設定された
    • Clojure のデータ型は Clojure 1.0 から Java シリアライズインターフェースを実装してきた
    • Java シリアライズは、クラス名、型階層、シリアライズ対象フィールドから生成される識別子が逆シリアライズ時に一致している必要がある
    • Clojure はバージョン間のシリアライズ一貫性を保証しないが、必要以上に互換性を壊さないよう、今後の制御性を高める変更を適用した
  • 依存関係もあわせて更新された
    • spec.alpha は 0.5.238 に更新
    • core.specs.alpha は 0.4.74 に更新

REPL でライブラリとツールを扱う機能

  • 開発中には JVM を再起動せずにライブラリを追加したい状況がある
    • 実験的な評価
    • プロジェクトに既知の依存関係を追加
    • 特定の作業のためにライブラリを追加
  • Clojure 1.12 は、REPL の状態を失わずにライブラリを追加する 新しい関数群 を提供する
    • add-lib: クラスパスにない lib をダウンロードして classloader に追加する
      • すでにクラスパスにある lib は更新しない
      • 座標がない場合、最新の Maven バージョン、または git リポジトリ名を推測できる場合は最新の git バージョンやタグを使う
    • add-libs: 複数の新しいライブラリとバージョンをまとめて解決する
    • sync-deps: deps.edn にはあるがまだクラスパスにない lib について add-libs を呼び出す
  • これらの関数は 開発中の REPL 利用 のみを意図している
    • 本番コードをビルドし維持する正しい方法は、引き続き deps.edn を使うこと
    • 3 つの関数はいずれも *repl* が true にバインドされているかを確認する
    • clojure.main/repl はこのフラグを自動的にバインドする
    • clojure.main REPL では新しい関数群が user 名前空間に自動で refer される
    • 他の REPL では (require '[clojure.repl.deps :refer :all]) が必要な場合がある
  • ライブラリ解決とダウンロードは tools.deps が担当する
    • 開発中のプロジェクトのクラスパスに tools.deps とその依存関係を入れないよう、Clojure CLI 経由で関数を別プロセスから呼び出す新 API も追加された
  • clojure.tools.deps.interop/invoke-tool はツール関数を 別プロセス で呼び出す
    • ツールのクラスパスは deps.edn に定義される
    • ツール依存関係をプロジェクトのクラスパスに追加する必要がない
    • add-lib 機能は invoke-tool を使って実装されており、ユーザーツールを対話的にビルドまたは呼び出す用途にも使える
    • 関数実行プロトコルは CLI reference で確認できる

外部プロセス実行 API

  • 既存の clojure.java.shell 名前空間に加えて、Java の新しいプロセス情報・制御・I/O リダイレクト API を活用する手段が加わった
  • Clojure 1.12 は新しい名前空間 clojure.java.process を追加する
    • Java の新しいプロセス関連 API を活用する
    • 従来の方式より使いやすく設計されている
  • 主な関数は次のとおり
    • start: ストリームを完全に制御でき、高度な用途向けに基盤となる Java オブジェクトへアクセスできる
    • exec: 外部プロセスを実行し、完了時に stdout を返す一般的なケースを扱う

Java 相互運用性の拡張

  • メソッド値 が追加され、Java メソッドを高階関数の中でより直接使えるようになった
    • 従来は Java メソッドを map などに渡すには明示的に関数でラップする必要があった
    • 手動ラップは冗長で、オーバーロードを区別するヒントが必要になったり、副次的なリフレクションやボクシングが発生したりすることがあった
    • これからは qualified methods を値位置で通常の関数のように使え、コンパイラが ラップ関数 を自動生成する
    • qualified method がオーバーロードのために解決できない場合、コンパイラはリフレクション呼び出しを生成する
    • 開発者は :param-tags メタデータで、望む単一のメソッドシグネチャを指定できる
  • Qualified method 構文ではクラスとメソッドを明示する
    • Classname/method: 静的メソッドを呼び出す Clojure 関数値
    • Classname/.method: インスタンスメソッドを呼び出す Clojure 関数値
    • Classname/new: コンストラクタを呼び出す Clojure 関数値
    • 静的メソッドとインスタンスメソッドを区別するには Classname/methodClassname/.method 構文を使う必要がある
  • :param-tags メタデータはオーバーロードされたメソッドの解決に使われる
    • 値として使う qualified method はクラス名とメソッド名しか与えないため、オーバーロードメソッドを解決できない
    • :param-tags[tag …] 形式のベクタで、各タグが望むシグネチャのパラメータに対応する
    • オーバーロードされていない型のパラメータには _ プレースホルダを使える
    • :param-tags を与えると、コンパイラはコンパイル時に単一メソッドへ解決できなければならない
    • 新しいメタデータリーダー構文 ^[tag …] はメンバーシンボルに :param-tags メタデータを付与する
  • 配列クラス構文 が追加された
    • Clojure はクラス名シンボルをクラスオブジェクト値や型ヒントとしてサポートしていたが、文字列以外では配列クラス構文を提供していなかった
    • これからは ComponentClass/#dimensions 形式のシンボルで配列クラスを参照できる
    • 例: String/1, java.lang.String/1, long/2
    • コンポーネントクラスは完全修飾クラス名、import 済みクラス、primitive のいずれにもできる
    • 配列クラス構文は型ヒントとしても値としても使える
  • Java の 関数型インターフェース 相互運用も改善された
    • Java の関数型インターフェースは @FunctionalInterface が付き、単一メソッドを持つ
    • Clojure 関数は arity が一致すれば、関数型インターフェースを受け取る Java メソッド呼び出しに渡せる
    • Clojure コンパイラはラムダアダプタを生成し、Clojure 関数を必要な関数型インターフェースへ暗黙に変換する
    • ループ内で繰り返しアダプタが生成されるのを避けるには、let バインディング名にヒントを付けて明示的に強制できる
  • Supplier 相互運用も改善された
    • 値を供給する Supplier を受け取るメソッドを呼ぶには、以前は reify でアダプタを書く必要があった
    • Clojure の IDeref 実装である delayfutureatom などは、これからは Supplier インターフェースも直接実装する

Stream 処理とコレクション性能の改善

  • Java API が返すことの増えている Stream を Clojure らしい方法で消費するための関数が追加された
    • Clojure 1.12 の関数型インターフェース対応とあわせて Stream 相互運用関数 が提供される
    • (stream-seq! stream) ⇒ seq
    • (stream-reduce! f [init-val] stream) ⇒ val
    • (stream-transduce! xf f [init-val] stream) ⇒ val
    • (stream-into! to-coll [xf] stream) ⇒ to-coll
    • すべての関数は終端 Stream 操作であり、Stream を消費する
  • PersistentVector は Java コレクションの stream 実装で使われる spliterator を直接提供する
    • spliterator は、より高速な並列走査のために分割できる iterator
    • PersistentVector の新しいカスタム spliterator は並列性をサポートし、性能が大きく改善された
  • dropnthrestnthnext とパーティション処理の効率が改善された
    • CLJ-2713 は、コレクションが逐次走査より効率的に drop できることを示す内部インターフェース IDrop を追加した
    • このインターフェースは persistent collection や rangerepeat のようなアルゴリズムコレクションに実装されている
    • 新関数 partitionvpartitionv-allsplitv-at は既存の対応関数より効率的で、realized seq のパーティションではなくベクタのパーティションを生成する

Var interning ポリシーの強化

  • 名前空間で var を intern することは、aliasing と異なり、すべての参照が同じオブジェクトを得られる 安定した参照 を作ることを意味する
  • これまでは intern 済み var が置き換えられるケースが一部あり、1.12.0-alpha1 でポリシーがより厳格になった
    • この状況が発生すると、"REJECTED: attempt to replace interned var #'some-ns/foo with #'other-ns/foo in some-ns, you must ns-unmap first" 形式の警告が表示される
  • このポリシーは、Clojure 1.11.0 で clojure.core に新しい関数、特に abs が追加されたことで表面化した問題の根本原因に対処するもの
    • 以前の Clojure バージョンでコンパイルされたコードが、clojure.core に新たに追加された関数名と同じ var 名を持っていると、1.11.0 ランタイムでロード時に unbound になることがあった
    • CLJ-2711 に加え、この領域の以前の修正である CLJ-1604 はロールバックされた

全変更一覧

1件のコメント

 
GN⁺ 2024-09-06
Hacker News の意見
  • 本当に大型リリースで、素晴らしい新機能がたくさんある
    個人的には add-libs がいちばん気に入っている。これで、問題再現用の単一ファイルデモや最小例を作れるようになり、実行可能な小さなコード片を共有するハードルが大きく下がった
    Java のボイラープレートなしで Java ライブラリもデモできる。REPL でいろいろ試したあと、HN のコメントのような場所にコードを貼り付ければ、誰でも同じ「設定」をそのまま再現して実行できる。リポジトリをクローンする必要もない

    • Groovy を覚えている人がいるかは分からない。Groovy には、説明した add-libs と実質的に同じことをする @Grab アノテーションがあり、スクリプト作成にとても便利
    • Java にも今では REPL とスクリプティング対応があるが、add-libs のような機能はまだ利用可能なメタコマンドにはない
  • Clojure/conj 2024 までこのリリースを遅らせるのだと思っていた。特に根拠があったわけではないが、Clojure 1.10 が Clojure/conj 2021 の頃に出て、Datomic の無料化発表も Clojure/conj 2023 の序盤にあったからだ
    それでもまだ spec2 を待っている。今は spec の硬直性を Malli で迂回しているが、Clojure における第一級市民ではない。主な理由はマクロを検査できないことで、これは Clojure コンパイラの設計上意図された部分だ。ただし Malli のスキーマをデータとして操作すれば、schema/select のアイデアはまねできる
    関数型インターフェースの変更のおかげで、今では (defmacro ->Consumer [f] ...) のようなユーティリティマクロを維持する必要なく、関数をそのまま渡せる

    • “maybe not” を見て schema/select が本当に必要になり、Malli 用のライブラリを作った: https://github.com/eval/malli-select
    • 「マクロを検査できない」とはどういう意味なのか気になる。spec でマクロに s/fdef を付けられるし、コンパイル時に呼び出しを検査する
      実際、Clojure core も spec でマクロ呼び出しを検査していて、誤って呼び出すとスタックトレースにその痕跡が見えることがある。別の意味で言っているのか気になる
  • 新機能が山ほど入っているのに既存コードがそのまま動くのが本当に良い。互換性破壊を避けようとする継続的な努力が光っている

  • Clojure についてもっと知りたいなら、10月23〜25日にバージニア州 Alexandria で開催される Clojure/conj カンファレンスを見るとよい: https://2024.clojure-conj.org

  • add-libssync-deps が入ったのはうれしい。これでセッションをわざわざ閉じる理由はほとんどない、あるいはまったくないように思える
    今回のリリースは以前のリリース群とはスコープがかなり違って見えるし、入っている内容も多くて興味深い。ただ、ペースが上がったせいで、数リリース後に絡み合った塊のようにならないことを願う

    • Rich Hickey と Clojure Team は非常に慎重な設計者たちなので、あまり心配しなくてもよいと思う
    • まったくの推測だが、Rich Hickey が nubank を去った後の最初のリリースなので、彼がより多くの関心を注げたのかもしれない
  • 関数型インターフェースの変更は非常に大きい。Clojure は慎重な相互運用を通じて Java に近い位置にあるときが最も良く、今回の変更は大きな隙間を一つ埋めてくれる

  • spec はどうなったのか気になる。放棄されたのだろうか?期待できるニュースがあるのか気になる

    • spec は引き続き存在し、使われている。後続作業も多く進んだが、いくつかの事項について何をするか検討している間、止まっている状態
  • かなり堅実なリリースに見えるし、Clojure が今も順調なのはうれしい

    • 本当に順調なのか気になる。新しいプロジェクトで使うか評価中で、Clara[0] とあわせて検討している。ただ、以前ほど主流ではないようだし、エコシステムも前よりまばらになった感じがする
      荒らすつもりではない。選びたいし、工学的には良い判断に見える。だが人気とコントリビューターが急落しているなら、近い将来に足かせになる可能性もある
      [0] https://www.clara-rules.org/
  • 既存の開発者を Clojure 開発者へ転換させるのが、これまでになく簡単になった
    最初に直面する大きな問題[0]はコードを読むことだが、ChatGPT や Claude のような AI は既存の Clojure コードを説明するのが非常に得意。その結果、開発者のオンボーディングがずっと速くなり得る
    [0] 数週間もすれば Clojure を読むのが自然になり、以前は読めなかったという事実すら忘れてしまう

  • 素晴らしい改善が多い。普段手が伸びる主な Lisp 系言語