**Fennel 上の Clojure ― 第一部:永続的なデータ構造**

2026/04/07 11:09

**Fennel 上の Clojure ― 第一部:永続的なデータ構造**

RSS: https://news.ycombinator.com/rss

要約

日本語訳:

著者は、Clojure スタイルの不変性、データ構造、および非同期サポートを Lua に持ち込む Fennel ライブラリ群を作成しました。
fennel-cljlib は 2019 年に開始され、コア Clojure 関数、マクロ、遅延シーケンス、テストユーティリティ、および

clojure.core.async
の移植版を公開しました。その唯一の本格的な用途は fenneldoc ドキュメントツールでした。
2024 年 3 月に著者は ClojureFnl を開始し、fennel-cljlib に依存する Clojure‑to‑Fennel コンパイラを作成しました。このコンパイラはほとんどの
.cljc
ファイルをコンパイルできますが、まだ完全な標準ライブラリサポートはありません。
itable
ライブラリにおける初期の不変構造は Lua のメタテーブルでコピーオンライトを使用していたため、非常に遅い操作(
immutableredblacktree.lua
にも同様の問題)が発生しました。これを解決するために immutable.fnl が作成され、永続的な HAMT ハッシュマップ/セット、ベクター、およびソート済みマップ/セット用の赤黒木が実装されました。
HAMT は Lua のパフォーマンス制約をバランスさせるために 16 分岐で構成されており、操作は O(log₁₆ N) です。実際のサイズではほぼ定数時間となります。ベンチマークでは、永続的なハッシュマップとベクターはネイティブ PUC Lua テーブルより 30–200 倍遅いですが、LuaJIT では約 10–40 倍遅く、トランジェントを使用するとパフォーマンスが 2–3 倍向上します。
ハッシュ化には djb2 アルゴリズムとランダムソルトを使用し、可変 Lua テーブルと永続コレクション間の衝突を回避しています。永続ベクターは 32 分岐ビットシフトインデックス走査(O(log₃₂ N))を採用し、
nil
キー/値もサポートします。赤黒木は Okasaki の挿入と Germane & Might の削除をフォローしており、操作は O(log N) ですが Lua テーブルより約 100 倍遅くなります。
永続リストは
{head, tail}
テーブルで再実装され、イテレータからの遅延構築と型検出を改善しました。永続キューはリンクドリスト前部とベクタ後部を組み合わせて O(1) enq/deq を実現し、必要に応じてベクターを遅延でリストへ変換します。
今後の計画としては、ClojureFnl の
.cljc
ファイルとの互換性向上、標準ライブラリ機能の追加、およびトランジェントや遅延シーケンスなど永続コレクションのさらなる最適化が挙げられます。 Fennel 開発者にとっては、不変性と非同期パターンを通じてより安全なコードを書けるようになりますが、性能トレードオフが高い時間的制約の Lua プロジェクトでの採用に影響する可能性があります。

本文

2019年頃に始めたプロジェクト

2019年のどこかで、Clojure の機能を Lua 実行環境に持ち込むことを目的とした fennel‑cljlib を開発しました。
これは Fennel 用ライブラリで、

clojure.core
名前空間の基本的な関数・マクロを実装したものです。

私の目標はシンプルでした。Clojure でコーディングするのが好きなのに、趣味プロジェクトとして使うことはほとんどありませんでした。そのため、Fennel が「Clojureっぽく」感じられるようにしたいだけです(既存の機能を超えて)。

このライブラリは年々拡張されてきました。
遅延シーケンス、イミュータビリティ、

clojure.test
と Kaocha に触発されたテストライブラリ、さらには
clojure.core.async
の移植まで行いました。
これは情熱プロジェクトであり、実際にソフトウェアを書き込むことはほとんどありませんでした。
唯一の例外が fenneldoc です――Fennel ライブラリ向けのドキュメント生成ツールで、真面目なプロジェクトで使われる例を見たことはありません。

それは単純な理由があります。実験的だったからです。角を削っており、Clojure に触発された Lisp である Fennel は Clojure のように機能指向プログラミングと結びついていません。
事実上、私はこのライブラリを真面目な用途にはまだ推奨しません。


新プロジェクト:ClojureFnl

最近、新しいプロジェクト ClojureFnl を始めました――fennel‑cljlib を基盤にした Clojure → Fennel コンパイラです。
開発はまだ初期段階ですが、数か月間プライベートで作業しており、3 月に動く方法を見つけました。

ClojureFnl REPL
ClojureFnl v0.0.1 – Fennel 1.6.1 on PUC Lua 5.5

(defn prime? [n]
  (not (some zero? (map #(rem n %) (range 2 n)))))
#<function: 0x89ba7c550>

(for [x (range 3 33 2)
      :when (prime? x)]
  x)
(3 5 7 11 13 17 19 23 29 31)

しかし、問題がありました。
itable ライブラリで実装した不変データ構造には重大な欠陥があったのです。このライブラリはコピーオンライトと Lua のメタテーブルを組み合わせた単純なハックで、不変性を強制していました。その結果、すべての操作が極めて遅くなっていました。

実験としては十分でしたが、ClojureFnl をさらに発展させるには置き換える必要があります。
同じ問題は

immutableredblacktree.lua
(コピーオンライトの赤黒木実装)でも起こりました。変更ごとに木全体をコピーしていたためです。

連想テーブルについてはそれほど大きな問題ではありませんでした――通常、マップには少数のキーしかなく、itable は変更が必要なレベルだけをコピーしました。
例えば、10 個のキーを持つマップがあり、その各キーにさらに 10 個のキーを持つマップが入っている場合、外側のマップでキーを追加・削除・更新すると、外側の 10 個のキーだけをコピーすれば済みます(内部マップは不変です)。

しかし配列の場合は通常状況と大きく異なります。配列は多くのインデックスを保持し、入れ子になることが少なく、変更ごとにコピーするとコストが急増します。

一部のパフォーマンス問題はトランシエント(遅延)版を自作して緩和しましたが、Clojure のデータ構造の美点はこの最適化なしでも高速であることです。


本格的な永続データ構造

Clojure はハッシュマップとセットに Persistent HAMT(ハッシュアドレス可能マルチテープ)、ベクタにはビット分割トライを使用しています。
ソート済みマップ・セットは、構造的共有を行う不変赤黒木実装です。

Lua で既存の HAMT 実装を調べましたが:

  • hamt.lua
    (mattbierner/hamt ベース) – 未完成
  • ltrie
    – トランシエントなし、ハッシュセット・順序付きマップ・複合ベクタ/ハッシュ未実装
  • Michael‑Keith Bernard の gist – カスタムハッシングなし

これらを使うこともできましたが、Fennel ライブラリとして埋め込み可能な Clojure コンパイラに組み込むためには Fennel で書かれた実装が必要でした。
そこで immutable.fnl を作成しました。

  • HAMT ベースのハッシュマップとハッシュセット
  • ベクタ
  • より良い永続赤黒木
  • 遅延リンクリスト

永続ハッシュマップ

Lua のネイティブハッシュを利用した Persistent HAMT から始めました。
構造は 16 分岐のトライで、

O(log₁₆ N)
操作――実際にはキー数が多くてもほぼ定数時間です。

Clojure は分岐係数 32 を使いますが、Lua では popcount が高価になり、変更ごとにより大きな疎配列をコピーすることになります。
分岐係数 16 にすると、50,000 エントリのマップは約 4 レベル(32 分岐なら約 3 レベル)深さで、妥当な妥協点だと判断しました。

ベンチマーク(PUC Lua 5.5、Fennel 1.7.0‑dev)

操作Lua tablePersistent HashMap比率
50k ランダムキー挿入2.05 ms164.80 ms80.3×
50k ランダムキー検索0.83 ms92.51 ms110.8×
全削除0.78 ms170.78 ms219.8×
10% 削除0.14 ms19.50 ms136.4×
50k 要素反復1.74 ms6.64 ms3.8×

トランシエントを使うと若干性能が向上しますが、ネイティブテーブルには遠く及びません。

LuaJIT の結果はオペレーション単位でコストが低減しますが、相対的な遅さは依然として大きいです。

永続ベクタ

ベクタは 32 分岐トライを用い、ハッシュではなく直接インデックスアクセスを行います。
操作は

O(log₃₂ N)
;追加は摂動的に
O(1)
です。

ベンチマーク(PUC Lua 5.5)

操作Lua tablePersistent Vector比率
50k 要素挿入0.19 ms21.07 ms109.7×
50k ランダムインデックス検索0.47 ms14.05 ms29.7×
50k ランダムインデックス更新0.32 ms70.04 ms221.6×
50k 要素ポップ0.25 ms24.34 ms96.2×
50k 要素反復0.63 ms10.16 ms16.2×

LuaJIT の結果はやや良くなりますが、依然としてネイティブテーブルより遅いです。

永続赤黒木

ソート済みマップ・セットには Okasaki の挿入アルゴリズムと Germane & Might の削除アルゴリズムを採用しました。
操作は

O(log N)
です。

ベンチマーク(PUC Lua 5.5)

操作Lua tablePersistentTreeMap比率
50k ランダムキー挿入2.10 ms209.23 ms99.8×
50k ランダムキー検索0.88 ms82.97 ms94.2×
全削除0.74 ms173.76 ms234.8×

LuaJIT はさらに遅くなる傾向があります。

永続リスト・キュー

旧メタテーブルスワップ方式の代わりに

{:head … :tail …}
テーブルを使って遅延永続リストを再実装しました。
リストインターフェースはイテレータから構築できるようになり、カスタムデータ構造と互換性があります。

永続キューはフロントリンクリストとリアー永続ベクタで構成され、enqueue/dequeue が高速です。


ClojureFnl

以上が ClojureFnl の基盤となるデータ構造の一部です。
より良い実装を手に入れたので、コンパイラ自体に集中できます。
次回の記事ではコンパイラについて触れる予定ですが、また別のことに気が散るかもしれません。

同じ日のほかのニュース

一覧に戻る →

2026/04/11 7:16

MacBook の角を削り取ること (「Filing the corners off my MacBooks」という表現は、MacBookの角をサンドペーパーやファイルで磨き取り、角を丸めている様子を指します。)

## Japanese Translation: ### 改良された要約 著者は、2026年4月にMacBookの鋭い角を削る方法について説明し、手首に快適に感じられる滑らかでより人間工学的なエッジを目指したと述べています。Apple のアルミニウムユニボディ設計はこのような改造を可能にしますが、狭いノッチは不快感を与えることがあります。そのため、小さな半径の曲線を大きなものに統合しながら、機械を通過してしまわないよう段階的に作業することに重点を置いています。保護措置として、スピーカーとキーボードをテープで覆い、アルミニウム粉塵から守り、ファイル作業中は軽い圧力でラップトップを工作台にクランプしました。プロセスは粗いファイルで始め、その後 150 グリットのサンドペーパーで研磨し、最後に 400 グリットのサンドペーパーで仕上げました。著者は完成した表面品質に満足していると述べています。写真には数か月の使用で蓄積された傷やへこみが写っており、調整が必要だった理由を示しています。この改造は作業用コンピュータ上で行われたものであり、将来の作業用コンピュータも同様の変更を受ける可能性があることを示唆しています。著者は自分のノートパソコンを改造したい他者に対して助けを提供すると述べています

2026/04/11 9:10

アーテミス II は無事に海に帰還しました。

## Japanese Translation: > トランプ大統領はアーテミスIIクルーを称賛し、「現代の開拓者」と呼び、将来の火星ミッションへの示唆を投げかけました。オリオンカプセルは2026年4月10日、サンディエゴから40–50マイル離れた太平洋で午後8時7分(ET)に正確に着水し、乗組員4名が搭乗していました。回収は海軍潜水士と16〜20人のチームによって行われ、カプセルを安定させるためにシーアンカー、膨張式カラー、および筏を取り付けてから乗組員を救出しました。 > > 着水後、カプセルのシステムは電源を切りましたが、短時間の技術的な不具合により完全な電源復帰が回収チーム準備完了まで遅れました。再突入中、宇宙船は等離子体形成による6分間の通信遮断を経験し、熱シールドで約5,000°F(約2,760°C)の温度に達し、秒速約24,661mph(≈3.9Gs)まで加速しました。 > > 乗組員はiPhoneを使用して地球の最終写真を撮影し、「アースセット」と宇宙からしか観測できない月食も収録しました。また、指揮官レイド・ワイズマン氏の故人の妻に敬意を表し「キャロル」という月面クレーター名を付け、別の名前として「インテグリティ」の提案も検討しました。 > > 着水後、カプセルはUSS John P. Murthaへ運ばれ医療点検が行われ、その後海軍基地サンディエゴへ移送されてからケネディ宇宙センターに戻り検査とデータ解析が実施されました。ミッションは地球から最も遠い距離で252,756マイルという新記録を樹立し、アポロ13より4,000マイル以上離れた位置で月面上空4,067マイルを超える接近を達成しました。ミッション・コントロールは着水を「完璧な的中」と表現し、全システムが乗組員救出前に完全に機能していたことを確認しました。 > > CBSニュースは午後7時30分(ET)から着水の午後8時7分までライブ放送を行い、視聴者はオンラインまたはモバイルデバイスで視聴できました。乗組員の総ミッション期間は約9日1時間31分で、地球と月周回で推定694,481マイルに相当する距離を移動しました。 > > 成功裏に帰還したことでNASAのオリオン回収手順への信頼が強化され、将来の深宇宙計画(潜在的な火星ミッションを含む)を支援し、ライブメディア報道による公共の関与も向上しました。

2026/04/11 4:10

ウガンダで研究者らが指摘:チンパンジーは8年間にわたり「内戦」に陥っている ---

## Japanese Translation: (以下は日本語訳です) **改訂要旨** ウガンダのキバレ国立公園にあるンゴゴチンパンジーコミュニティ―かつて最大規模の野生集団(約200頭)―が、2015年6月の脱走をきっかけに、西部と中央という二つの対立派閥へと分裂しました。脱走後は6週間の回避期間が続き、その後対立が激化していきました。2018年までには両集団が完全に別々になり、西部チンパンジーは中央メンバーを標的にした攻撃を開始しました。 分裂以降、研究者たちは**24件の殺害事件**(少なくとも7頭の成人雄と17頭の乳幼児が中央集団から死亡)を記録しており、実際の死亡数はさらに多い可能性があります。暴力の主な要因として3つが挙げられます: 1. 2014年に5頭の成人雄と1頭の雌が死亡し、社会的ネットワークが崩壊したこと; 2. 2015年にアルファ雄が交代し、初期分裂と相まって影響を与えたこと; 3. 2017年に発生した呼吸器感染症流行で25頭のチンパンジー(中でも重要な集団間結びつき役)が死亡したこと。 著者アーロン・サンドルは、チンパンジーの領土性と外部との敵対的相互作用が、人間社会における宗教や政治といった文化的構造なしでも致命的な集団攻撃を引き起こす可能性があると強調しています。この研究は*Science*誌に掲載され、同誌のポッドキャストで取り上げられました。相関関係だけが戦争を説明できるという示唆は、人間の紛争根源を再考する必要性を訴えています。また、資源競争と雄対雄の繁殖闘争もエスカレーションに寄与した可能性が高く、集団分裂が社会を危険にさらす警告として提示されています。 この要旨は主要な事実を全て保持しつつ曖昧な表現を排除し、研究の広範な示唆を明確に記述しています。

**Fennel 上の Clojure ― 第一部:永続的なデータ構造** | そっか~ニュース