ClojureScript 1.12.145: Lisp的なWebにAsync/Awaitをもたらす

ClojureScriptチームは、エコシステム内で最も要望の多かった機能の一つである asyncawait のネイティブサポートを導入するバージョン 1.12.145 のリリースを発表しました。このアップデートは、ClojureScript開発者が現代的なJavaScript APIとやり取りする方法における重要な転換点となり、ECMAScript 2016標準とのよりシームレスな統合へと向かっています。

長年、ClojureScriptユーザーは非同期操作を扱うために外部ライブラリや複雑なパターンに頼ってきました。コンパイラがネイティブなJavaScriptの async 関数を出力できるようにすることで、ClojureScriptは、追加の依存関係のオーバーヘッドなしに、Promiseや非同期フローを扱うための第一級の手段を提供します。

ClojureScriptにおけるAsync関数の実装

新しい機能は、関数に ^:async というヒントを与えることでトリガーされます。コンパイラがこのヒントに遭遇すると、JavaScriptの async 関数を生成します。これらの関数内では、await 特殊形式を使用して、Promiseが解決されるまで実行を一時停止できます。

以下の実装を検討してください:

(refer-global :only '[Promise])

(defn ^:async foo [n]
  (let [x (await (Promise/resolve 10))
        y (let [y (await (Promise/resolve 20)]
            (inc y))
        ;; not async
        f (fn [] 20)]
    (+ n x y (f))))

この構文はテストフレームワークにも拡張されており、開発者がより自然に非同期テストを書くことができるようになっています:

(deftest ^:async defn-test
  (try
    (let [v (await (foo 10))]
      (is (= 61 v)))
    (let [v (await (apply foo [10]))]
      (is (= 61 v)))
    (catch :default _ (is false))))

インターオペラビリティのトレードオフ:ネイティブ vs. CSP

async/await の導入は、Clojureにおける非同期プログラミングの哲学に関する、コミュニティ内での健全な議論を巻き起こしました。長い間、ClojureScriptにおける並行性のゴールドスタンダードは、Communicating Sequential Processes (CSP) を実装する core.async でした。

一部のコミュニティメンバーは、複雑な状態管理やプロセス間の通信にはCSPモデルが優れていると主張しています。あるユーザーが指摘したように、async関数をCSPでラップすることは、Clojureにすでに堅牢なパターンが存在するため、「これを扱うためのより良い方法」と見なされることがよくありました。他の人々は、JSスタイルの async キーワードを導入することが、本当にアップグレードなのか、それともロジックをチャネルに押し込むというClojureの核心的な哲学から乖離しすぎているのかを疑問視しています。

しかし、この変更の主な推進力は インターオペラビリティ(相互運用性) です。Clojureの調査では、asyncサポートがJSインターオペラビリティのための要望リストの中で上位を占めていました。ネイティブな async/await をサポートすることで、ClojureScriptは、現代的なBrowser APIや人気のJavaScriptライブラリ(そのほとんどがPromiseベースです)との一般的なやり取りにおいて、Promesaのようなライブラリを必要としないようにします。

技術的なハードルを克服する

この機能への道のりは、すぐには実現しませんでした。コミュニティの知見によると、以前の async/await 実装の試みは、以下の要因によって妨げられていました:

  1. コンパイラの複雑さ: これを実装するには、ClojureScriptコンパイラ全体にわたる深い変更が必要でした。shadow-cljs の作成者を含む著名なコミュニティメンバーによる以前の試みは、これが非常に困難なタスクであると結論づけていました。
  2. Language Divergence(言語の乖離): Clojure(JVM版)との一貫性を維持することと、JSランタイムに不可欠なツールを提供することの間に緊張関係があります。await はすでに clojure.core におけるキーワードであるため、ClojureScriptにおけるこの特殊形式の導入は、Web環境の特定のニーズに応えるための計算された乖離を意味します。

結論

ClojureScript 1.12.145 は、単なる構文のアップデートではありません。それは、現代的なWeb開発のニーズに対する実用的な対応です。core.async のCSPモデルが複雑な並行処理において強力なツールであり続ける一方で、ネイティブな async/await は、JavaScriptエコシステムとやり取りするための軽量で摩擦のないパスを提供します。現代的なフロントエンドを構築する開発者にとって、ターゲットプラットフォームにネイティブな感覚で非同期コードを書けることは、生産性とインターオペラビリティの大きな利点となります。

Sources