Lisp: アイシングかケーキか?
(dthompson.us)- Spring Lisp Game Jam 2024 では 48本のゲーム が投稿され、新記録を達成。参加作は、Lispを載せる方式と、スタック自体をLispで作る方式にはっきり分かれた
- アイシング方式 は、C/Rust/Lua ベースのプログラムの上に Lisp をスクリプティング層として載せて素早く結果を出せるが、下層の静的言語とツールチェーンに強く縛られる
- ケーキ方式 は、プログラムの大部分を Lisp で書き、C FFI を最小限に抑えてより深い制御権を得るが、ライブラリ実装・ラッパー作成・Web 配布のコストが大きくなる
- Game Jam では、Fennel+love2d と S7+raylib はアイシング寄りで、Guile+Chickadee はケーキ、Hoot+HTML5 canvas は Scheme ベースの Wasm ツールチェーンのおかげでよりケーキに近い
- Lisp の比重が大きくなるほど、ライブハッキング、メモリ安全性、Lisp/C 境界の削減、ハックしやすさが増し、Guix・Trial・Pre-Scheme のようなプロジェクトも同じ方向性を示している
Spring Lisp Game Jam 2024 の投稿状況
- Spring Lisp Game Jam 2024 は1週間前に終了し、48本のゲーム が投稿されてジャムの記録を更新した
- 参加者はその後1週間、互いのゲームを遊び、評価した
- 投稿作の言語別分布は以下の通り
- Guile: 15本、31%
- Fennel: 10本、21%
- Clojure: 5本、10%
- Common Lisp: 5本、10%
- Racket: 4本、8%
- Elisp: 4本、8%
- S7: 3本、6%
- Kawa: 1本、2%
- Owl: 1本、2%
- Guile の比率: {p:31}
- Scheme 実装をひとつの
schemeカテゴリにまとめなかった理由は、Scheme の仕様が小さく、Guile・Racket・S7・Kawa がそれぞれ異なる目的を持つ実装だからである - 今回のジャムでは Guile が初めて最多投稿言語となった
- Guile のゲーム 15本のうち 11本は Hoot で作られた Web ゲームだった
- Hoot は Spritely Institute が開発中の Scheme-to-WebAssembly コンパイラである
- 11本のうち 2本は公式の Spritely プロジェクトである
- Spritely Institute はジャム開始前に Hoot でゲームを作ってみるよう呼びかけ、多くの参加者が応じた
- 通常、このジャムで最も人気のある言語は Fennel で、Fennel は Lua にコンパイルされる Lisp である
- S7 を使った3本のゲームも、Lisp をゲーム開発に使う方法と結びつく事例となっている
Lisp をアイシングとして使う方式
- アイシングパターン は、C や Rust のような静的言語で作られた「ケーキ」の上に、Lisp をスクリプティング言語のように載せるアプローチである
- 通常は、より大きなプログラムの中に Lisp インタプリタを組み込む
- アプリケーションの高水準部分を Lisp で書きたいなら、最も速い道になりうる
- 適切なインタプリタまたはコンパイラが必要である
- アプリケーションに必要なフックを追加する方法も必要である
- プログラムの主要部分が C や Rust で書かれていれば、emscripten で WebAssembly にコンパイルして Web に配布できる
- 素早く満足のいく結果を得られるが、静的言語とそのツールチェーンに強く結びつく
- 代表例は以下の通り
- S7 は組み込み可能な Scheme である
- Guile も C プログラムの拡張に使えるが、実行ファイル内にインタプリタを入れるより、通常は
libguileに動的リンクする - Fennel は、Lua 拡張ポイントを持つ既存アプリケーションを活用し、Lisp 風言語を Lua にコンパイルする
Lisp をケーキとして使う方式
- ケーキパターン は、できるだけ多くのソフトウェアスタックを Lisp で実装するアプローチである
- Lisp を非 Lisp プログラムの中に入れるのではなく、プログラムの大部分を Lisp で書く
- 必要なら外部関数インターフェース (FFI) で共有ライブラリを呼び出すが、その利用は最小限に抑えるのが望ましい
- 結果が出るまでにはより時間がかかる
- 選んだ Lisp 実装に存在しないライブラリを自分で実装しなければならない
- 避けられない C の共有ライブラリにはラッパーを書く必要がある
- プロジェクトは簡単には emscripten の対象にならず、Web 配布がより難しくなる
- このアプローチは古典的な embed vs. extend 論争にもつながっている
- Guile はアイシングにも使えるが、ケーキとして使うときにより強みを発揮する
- Guile の初期ビジョンは、Scheme インタプリタを追加して他のプログラムを Emacs のようにすることだった
- 現在の良い実践は、最初からプログラムを Scheme で書くことである
- Common Lisp もケーキアプローチの好例である
- SBCL のような実装は優れた C FFI を提供する
- 効率的なネイティブ実行ファイルにコンパイルできるため、性能上の理由で C を使いたくなる場面を減らせる
Game Jam の事例で見るアイシングとケーキ
-
Fennel + love2d
- love2d は長年、個人または小規模チームのゲーム開発で人気の選択肢だった
- love2d は Lua インタプリタを内蔵した C++ プログラムなので、Fennel にとって相性のよい対象である
- ほとんどの Linux ディストリビューションが love2d をパッケージしているため、
.loveファイルをネイティブで実行しやすい - emscripten により、love2d ゲームを Web にも配布できる
- そのため、Fennel のゲームの大半は love2d を使う
- ./soko.bin と Gnomic Vengeance がこのスタックを使っている
- Fennel+love2d は Lisp as icing の完全な例である
- Fennel はスタックの最上層にあり、Lisp を下位層へ広げる道は事実上ない
- 現時点で最も成功している Lisp ゲーム開発スタックである
-
S7 + raylib
- 今回のジャムでは GhostHop と Life Predictor の2本が S7+raylib スタックを使っている
- Raylib は複数の高水準言語バインディングを持つ C ライブラリで、ここ数年で人気が高まっている
- S7 も C で実装され、容易に組み込めるため、この組み合わせは emscripten で Web に配布しやすい
- S7+raylib も Lisp as icing の事例であり、今後のジャムでさらに人気を得るか注目に値する
-
Guile + Chickadee
- Chickadee は Guile 向けのゲームライブラリで、レンダリングを含む興味深い部分のほぼすべてを Scheme で実装している
- 最近のジャムでは Turbo Racer 3000 と Bloatrunner の2本が Chickadee で作られた
- Guile+Chickadee は Lisp as cake の事例である
- Chickadee は画像、音声、フォント読み込みのような低水準処理のために一部の C ライブラリをラップしているが、自身のコードは純粋な Scheme で書かれている
- 行列やベクトルの数学処理もすべて Scheme で実装されている
- love2d や raylib に匹敵するレンダリングプリミティブ一式を提供し、それもまた Scheme で実装されている
- 他の Lisp ゲームライブラリが nanosvg のような C ライブラリを使うことが多いのに対し、Chickadee では Scheme によるベクターグラフィックス描画の実装も進んでいる
- Chickadee は Guile コンパイラと仮想マシンの限界を押し広げ、その過程で Guile 自体も改善された
- ただし、その大半は一人の開発者が限られた余暇時間で進めてきたため、より人気のあるゲーム開発ライブラリと機能面で並ぶには長い時間がかかる
- 現状でも、この目的にはかなりうまく機能している
-
Hoot + HTML5 canvas
- Hoot は Scheme-to-WebAssembly コンパイラである
- Hoot は C で書かれた Guile VM を emscripten で Wasm にコンパイルするわけではない
- その代わり、完全な Wasm ツールチェーンと、Wasm を直接出力する Guile コンパイラ向けの新しいバックエンドを実装している
- Hoot はすべて Scheme で書かれている
- emscripten でコンパイルした C プログラムが線形メモリベースの Wasm 1.0 を対象とするのに対し、Hoot は GC 管理ヒープ型を持つ Wasm 2.0 を対象とする
- この構造により、Hoot のバイナリは ガベージコレクタを含めて配布しない
- そのため、emscripten でコンパイルした Lisp ランタイムよりはるかに小さい
- ある Hoot ゲームの Wasm バイナリは 2MiB 未満で、確認した love2d ゲームの
love.wasmはほぼ 6MiB だった - Hoot のプログラムは JavaScript と容易に相互運用できる
- Scheme オブジェクトを JavaScript に簡単に渡せる
- JavaScript オブジェクトも Scheme に渡せる
- 両者のオブジェクトが同じヒープ上で管理されるためである
- ブラウザ API には Wasm import としてアクセスできるため、ゲームでは組み込みの HTML5 canvas API が手軽な 2D レンダリングの選択肢になる
- 今回のジャムでは Hoot を使ったゲームが 11本あり、Cirkoban と Lambda Dungeon も含まれている
- Hoot+HTML5 canvas は、大部分が分厚いケーキで、一部にアイシングが混ざった形である
- Hoot の起動 には1年と相当な資金がかかった
- emscripten を使わず、独自ツールチェーン を構築し、Guile コンパイラも拡張した
- Guile VM 上で動く Wasm インタプリタもある
- 一方で、canvas API は非常に高水準である
- よりケーキに近い方法は、Hoot の JS FFI を使って WebGL または WebGPU を呼び出すことだ
- 今後の計画は WebGL/WebGPU 方向であり、それを可能にするには Wasm GC の改善が必要である
- もうひとつの目標は Chickadee を Hoot に移植し、Chickadee のゲームを love2d のゲームのようにネイティブとブラウザの両方で簡単に遊べるようにすることである
ケーキアプローチの限界と利点
- ケーキアプローチにも明確な限界はある
- 現代の環境は Lisp マシンの世界ではなく、最も高い Lisp ケーキでさえ、大半は C でできたより大きなケーキの上に載っている
- 現代の Lisp システムは、どこかの時点で下位層に到達する
- Emacs は C コアの上にある
- Guile VM は C で書かれている
- Hoot は V8 のような C++ ベースの大規模 JavaScript エンジン上で動作する
- Hoot のゲームは現在、WebGL/WebGPU ではなく HTML5 canvas で描画している
- OpenGL を使うには
libGLが必要である - Chickadee は
libGLを C FFI で呼び出すguile-openglを使う libpng、FreeType なども存在する
- すべてを Lisp で書き直すにはリソース面の負担が大きい
- それでも、スタックの一部を C のような言語から取り戻すことは小さな勝利である
- Lisp で書かれた部分はハックしやすく、一部はプログラム実行中の ライブハッキング も可能である
- GC 管理ランタイムのおかげで、一般にメモリ安全性を得られる
- FFI 呼び出しが減れば、Lisp/C 境界をまたぐオーバーヘッドが減り、安全性も高まる
- スタック内の Lisp の比重が増すほど、アイシングよりケーキに近づく
ゲームの外にあるケーキの事例
- Guix は、ケーキアプローチがどれほど強力になりうるかを示す好例である
- Guix は Nix プロジェクトの関数型パッケージングモデルを取り入れて再実装し、Nix 言語を Guile に置き換えた
- その理由は コードステージング、コード共有、ハックしやすさの向上である
- Guix は systemd の代わりに Guile で書かれた init システムも使っており、この選択も同じ理由による
- Guix は当初、理由もなく車輪を再発明していると批判されがちだったが、10年後の今では Lisp の利用を最大化しようとする頑固さがプロジェクト成功の核心になっている
- ユーザーは Guix のイディオムと少しの Guile を覚えるだけで、OS を思い通りに構成できる強力な能力を得られる
- Guix は、現代のハードウェア上で Lisp マシンに最も近い体験と見なせる
- Common Lisp では、Trial ゲームエンジンが、C ライブラリをラップする代わりに多くの部分を Common Lisp で実装した事例である
- Pre-Scheme のようなプロジェクトは、GC 管理ランタイムのさらに下層も、いつか Lisp で実装できるかもしれないという期待を与える
- Pre-Scheme は Scheme 48 で開発され、実際に利用されてきた
- NLnet grant によって、現代的な復活が期待されている
Lisp でより多くのスタックを作ろうとする方向
- 方向性は ケーキ 側に近い
- Lisp にできることの限界を押し広げ続けるプロジェクトがもっと必要である
- Lisp Game Jam で最も興味深いのはゲームそのものより、古く乾いた C からケーキの一切れを取り戻す小さな前進である
- Guile のゲーム開発では、Chickadee プロジェクトによって限界を押し広げ続けようとしている
- 結論は、Rust で書き直すことではなく Lisp で書き直すこと である
1件のコメント
Hacker Newsのコメント
客観的にソフトウェアのアプローチを比較する記事を最近ほとんど見かけないので、なおさらうれしかった
そういう記事を探そうとしても、最近は検索結果がSEOスパムを越えられないことが多い
Janetはゲーム向けに作られたように見えるし、Webサーバーやグラフィックスのような「バッテリー同梱」的な要素も意外と多そうなのに、Janetを使ったゲームが見当たらないのは少し意外だった
Lispとゲームの文脈では一度見てみる価値のある言語だと思う
s7が注目されているのはうれしい
Max/MSPのコンピュータ音楽環境にSchemeインタプリタを組み込むオープンソース拡張であるScheme for MaxでSchemeとして使ったことがあり、Guile、Clojure、Common Lispの中間あたりに位置しつつ、とても小さくて埋め込みやすい
Guileよりもずっと寛容なBSDライセンスなのもよい
第一級環境を持つCommon Lispマクロが好きなら、s7も気に入る可能性が高い
WASMでも非常に使いやすく、音楽教育プロジェクトでそのように使っている
SchemeからJS関数を呼び、逆にJSからSchemeを呼ぶ汎用関数を作るのも難しくなく、全体の流れはスムーズだった
iOSとAndroidのネイティブアプリで、グラフィックス以外のエンジンとしてs7とSQLiteをうまく埋め込めた
非常に高速で、FFIが良く、安定していて小さく、モバイルアプリ間で共有するコードや非常に高速な単体テスト、整ったツールチェーンから大きな恩恵を受けた
最終的には、モバイルではより実用的なLispであるFennelへ移行した
モバイルでs7を使っているチームはほぼ自分たちだけだった一方、Luaはモバイル拡張言語としてはるかに一般的で、r7rs Scheme互換の状況も影響した
デスクトップ開発にはGuileを使い、配布はs7で行っていたため、微妙な非互換性、たとえば引数の評価順のような問題によく引っかかった
s7もFennelも、どちらも素晴らしいプロジェクトとコミュニティを持っている
Spritely Instituteは、特にブログまで見てみることを勧めたい
何をしているところかはネタバレしたくないが、深く掘る価値のあるテーマと組織だ
ブログや関連リンク、プロジェクトを読むだけで10時間以上使った
https://spritely.institute/archive/
「私たちはLispマシンの世界ではなく、美化されたPDP-11の世界に生きている」という要約が印象的だった
ただ、sdlと一緒に使う「アイシング」もあるのか気になる
タグなしメモリを使うフォン・ノイマン機械という点では似ているが、32ビットマイクロプロセッサが一般化し、Lispコンパイラ技術がそれに合わせて進化した80年代半ばごろから、従来型CPUがLispマシンを追い抜き始めた
「タグなしメモリ」という部分も、CHERIのような流れを見ると今後も維持されるとは限らず、LispMスタイルのアーキテクチャが大きく復活する余地はある
数年間、居間にPDP-11/45を置いていて、その後は場所を減らすために8インチフロッピー2台付きのH-11 LSI-11/2を何台かに替えた
PDP-11はUnixの原形質のような存在であるだけでなく、概念的にもよく設計されていた
私たちが「1画面に収まる」ルーチンを好むように、PDP-11の小さな直接アドレス空間は大きすぎないモジュールを促し、モジュール性を後押しした
今では不十分か? もちろんそうで、とくにビッグデータではつらい
それでもPDP-11が成功し、今も痕跡が残っているのには概念的な理由がある
Cを直すほかの方法がなく、書き直されることのないコードが多すぎるからだ
Lispは主にリストを扱い、リストはメモリ中のあちこちにあるポインタをたどることになりうる
以前のCPUではメモリがおおむね同じランダムアクセス時間を持っていたので問題ではなかったが、現代のCPUはメモリポインタを同じ速度でたどれず、性能のためには局所性のルールに従う必要がある
そのため、CやFortranの配列のようなものを使うアルゴリズムは、Lispのリストベース版より常に速くならざるを得ない
最近のGuile Schemeの進展に期待している
最後に見たときとは違って、インタプリタ言語からちゃんとしたコンパイラを備えた言語になり、今ではHootでWASMコンパイルも可能になった
Clojure、uLisp、Common Lispには慣れているが、Guile SchemeはCommon Lispの余計な部分をかなりそぎ落とした感じがあり、特にGuixとShepardが定着するなら、コンパイルされるLispを手元に置いておきたい
Little LisperとSICP以外で、Guile Schemeを効果的に学べる良い資料があるのか気になる
今後作るものにGuileを使ってみようと思っていたので、面白く読んだ
WASM方面の作業もうまく進んでいるようだ
https://wingolog.org/archives/2024/05/16/on-hoot-on-boot
最近Clojureで3Dボス戦のプロトタイプを作った: https://prototype-game.pages.dev
以前はWeb開発全般が嫌いだったが、ClojureScriptは実際に楽しいものにしてくれて、もっと広く使われてほしい
後でPCの前にいるときにもう一度試してみる
どんなライブラリを使っているのか気になる
子どものころに本当に好きだったゲームの一つだ
https://www.youtube.com/watch?v=dcJFldES9dg
表に入れてほしいとは頼んだが、もう古い話題かもしれない
参考:
https://lispy-gopher-show.itch.io/logos-lisp-legend/devlog/7...
https://itch.io/post/10013482
https://ianthehenry.com/posts/janet-game/ があるのに Janet が抜けている
記事の著作権年は1899〜1907年になっているが、ヴィンテージっぽく見えないのは残念
スクリプティングにはまた Fennel に戻ったが、より多くの Lua ライブラリをそのまま使えて、ほぼどこでも、iPad の a-Shell でさえ問題なく動かせるからだ
このスレッドには役立つ参考資料があり、後でまた試してみるかもしれない: https://janet.zulipchat.com/#narrow/stream/409517-help/topic...
すでに実績のある本番向けの経路があったにもかかわらず、期限内にかろうじて遊べるものを作れただけで、「遊べる」という表現さえ少し大げさなくらいだ
Emacs Lisp でどんなゲームを作ったのかとても気になる
ゲームプログラミングを考えるとき、真っ先に思い浮かぶ選択肢ではない
Dunnet はもともと Ron Schnell が1982年に TOPS-20 上で動く Maclisp プログラムとして書いた
1992年に Emacs Lisp に移植されたが、単なる移植にとどまらず、新しい部屋、アイテム、パズルが追加され、MIT 中心のコンテンツは削除された
たとえばゲーム終盤の「endgame」コンピュータは、もともと MIT-SALLY という名前で MIT にあり、Chaosnet 経由でアクセスしていたが、GNU Emacs 版ではこうした古い MIT への言及は取り除かれている
その代わり、VAX 11/780 のようにより広く知られているが、やはり時代を感じさせる要素は意図的に残されている
https://en.wikipedia.org/wiki/Dunnet_(video_game)
元のものはここにある: https://github.com/Quogic/DunnetPredecessor/blob/master/foo....
https://lcolonq.itch.io/slgj2024-game-boy-gizmo
https://asquared31415.itch.io/disassembly
https://grindingstone.itch.io/pendulum