- JavaScriptの文法は入れ子の括弧やコールバックで複雑になりやすく、小さなUIでも多くのライブラリを引き込む肥大化が起こる
- WebAssemblyはブラウザで他言語を実行する道を開くが、PyodideのようにJavaScriptイベントループとの非同期接続コストが大きい
- ブラウザ資源とWebAssemblyメモリは限られているため、JavaScriptを置き換えようとするより交渉するアプローチが必要
- LispEは3.3MBのWASMバイナリ1つに450個以上の関数を収め、文字列・計算・行列・正規表現をまとめて提供する
evaljsとasyncjsでJavaScript関数やDOMを活用しつつ、複数の外部ライブラリの代わりに監査可能な単一バイナリでコードの肥大化を減らす
JavaScriptの肥大化とブラウザの制約
- JavaScriptの文法は丸括弧、中括弧、角括弧が重なり、閉じる順序を合わせる必要があるため、コードが複雑になりやすい
forEachコールバックの中で複数のオブジェクトに値を入れ、条件付きでキャッシュを更新する例が示される
newNames.forEach((name, i) => {
allAgentContents[name] = contents[i];
agentModes[name] = modes[i];
if (compiled[i]) agentCompiledCache[name] = compiled[i];
agentViewingCompiled[name] = viewing[i];
});
- 基本的なプログラミング言語なら通常含まれている処理のために、JavaScriptでは多くのライブラリをダウンロードすることになりがち
- CやC++でも作業には
includeが必要だが、JavaScriptライブラリは誰が実装し、誰が保守しているのか分かりにくいことが多い
- 小さなウィンドウに hello を表示するために「インターネットの半分」を読み込んでいるようなページも生まれる
- インターネットはJavaScriptに依存しており、TypeScriptで隠しても、ブラウザでページを開くたびに通らなければならない門番のままである
- ブラウザが究極のOSとなってWindowsやMac OSを不要にするという夢があり、その夢を実装する言語としてJavaScriptが選ばれた
WebAssemblyとJavaScriptの関係
- WebAssemblyは独自の機械語を持つ仮想マシンに近く、ブラウザ内で別の方法でコーディングする可能性を開く
- PyodideはブラウザでPythonを実行する印象的なエンジニアリング成果だが、JavaScript領域の中で動くコストも浮き彫りにする
- Pythonの
asyncioとJavaScriptイベントループという2つの非同期世界が相互に対話しなければならない
- 2つの世界の間の橋は脆弱で、すべての
awaitがどちらの世界に属するかを覚えておく必要がある
- ブラウザ資源は限られているため、初期の計算機科学の時代のように、各命令や構造をビット単位で見ていく考え方が必要
- 1981年に正確に15772 bytesしか残っていないコンピュータでプログラミングを始めた、という基準が示される
- 現代のブラウザメモリがその最初のコンピュータほど制限されているわけではないが、WebAssemblyは通常のプログラムのようにメモリを自由に所有できず、まず制限付きの方法で許可を受ける必要がある
- 核心となる姿勢は、JavaScriptと戦うのではなく交渉すること
LispEが示す代替案
- LispEは単一バイナリ内に450個以上の関数を提供する
- WASMバイナリのサイズは3.3 MB
- 文字列、計算、行列、正規表現処理の機能が1か所にまとまっている
- WASMバイナリはbinaries/wasmにある
- 小さなインタプリタだが、戻り値として文字列、
Float64Array、整数、実数、文字列配列を扱える
- LispEはLispベースなので、ASTが生きた構造として残る
- Lisp構文が合わない場合は、PythonやBasicと見分けがつきにくい言語向けのトランスパイル構文を使える
- LispEはJavaScriptと戦うのではなく、JavaScript関数やDOM機能を活用する形で協調する
JavaScript呼び出し: evaljsとasyncjs
- LispEの中でJavaScriptコードを実行するには**
evaljs** と asyncjs を使える
evaljsはJavaScriptコードを実行して値を受け取る
asyncjsはページに定義されたユーザーJavaScript関数と非同期コールバックを接続する
(setq a (evaljs "10 + 20 + 30")) ; execute some JS code
; call_llm is a user-defined JS function in the page
(asyncjs `call_llm("Implement a piece of code in Python to sort strings");` 'mycallback)
(defun mycallback(theresult) ...)
asyncjsは作業完了後に戻ってくるPromiseとして動作する
コードの肥大化を減らす
- 1995年のWirthの引用は、より速いコンピュータやより大きなモデルが問題を解決するのではなく、コードをスリムに保つべきだという結論につながる
- LispEはブラウザに公開される450個の関数を、監査可能な単一バイナリとして提供する
- 各機能を個別に実装しようとすると、数値計算用の
mathjs、コレクション用のlodash、文字列操作用のvoca、統計分布用のsimple-statisticsのようなライブラリを読み込む必要がある
- この方式では、それぞれ異なる作者、バグ、保守スケジュールを持つ数百MBのコードに膨らむ可能性がある
- LispEはこれらの機能を1つの保守されたコードで提供し、オープンソースなので全コードを監査できる
- Garbage Collectorが最悪のタイミングでコード性能を崩壊させることはないと見ている
- JavaScriptとは単純なAPI呼び出しで透過的に通信し、事前定義された構造を返せる
// floats here is a Float64Array
const floats = callEvalLispEToFloats(0, `(normal_distribution 100)`);
2件のコメント
なんだか唐突な記事で出典を疑ったけど、まさかNaverだったとは…。
でもやっぱり反応は良くないですね……。正直、JavaScriptが大げさに扱われた挙げ句、3.3MBのWASMまで載せてLispを使おうというのは、簡単には理解しにくいオーバーエンジニアリングですよね(笑)
Naverアカウントだった理由については、Claudiusというプロジェクトの開発者がコメントを残していて、自分はNaver Labs Europeで働いており、Naverがオープンソースプロジェクトとして承認したので投稿されたそうです。
Naverとはあまり大きな関係はなさそうで、ただ本当にLispを愛している人たちなんだと思います……
Lobste.rs の意見
JavaScript を軽量化すると言いながら、不透明な 3.3MB の WASM の塊を追加して、しかもアプリは Lisp で書けということなのかと思ってしまう。
純粋な JavaScript と追加依存 0 個だけでも、そのサイズの 10 分の 1 に届く前にかなり多くのことが作れる
この使い方には共感しないが、最近では珍しい奇妙さのある面白い Lisp 情熱プロジェクトで、すべてのドキュメントが人の手で書かれたと分かる独特な文体なのも良い
標準ライブラリに新しく入れる価値があるものは、いつでも提案を受け付けている。
最近追加されたものには 集合の和集合/積集合、
sum、base64などがある。ただ、微積分関数の要望はほとんど聞いたことがなく、文字列関連で継続的に要望があるのは
.reverseや.titleCaseくらいだ。.reverseはおもちゃ的な問題以外に説得力のある使い道があまりなく、.titleCaseは 実現可能ではあるが国際化データが必要なため議論が進行中 だ。しかも どちらもこのライブラリにはない
提案しても実際に反映されるまで 3 年かかるなら試す価値がないと感じるし、
utils/フォルダはいまひとつでも今すぐ作れると思っている面もある私の主張は、JavaScript に関数が足りないというより 配布モデルに関するものに近い。
標準ライブラリが充実していても、平均的なアプリは依然として推移的な npm 依存をメガバイト単位で積んで配布している。
LispE-as-WASM は 3.3MB、文書化された関数は約 450 個、C++ コア、GitHub で公開されたソースを備えており、監査可能なランタイムがどのようなものになり得るかを見るための実験だ。
実際、その上にエージェントハーネスを構築し、LispE のルールをブラウザ内でその場で実行している。
JavaScript のイベントループはとても気に入っている
誰かが 文法が肥大化していると言い出すと身構えてしまう。
最小限の文法は、視覚的な区別のために異なる文字を使う文法よりも、はるかに読みにくいことがある。
LispE が代案として出してくるものを見るとこうだ。
... !?
https://github.com/naver/lispe/wiki/5.3-A-la-APL
このプロジェクトへの入り口が少し変で、https://github.com/naver/lispe/wiki/1.-Introduction のほうが良さそうに見える。
WASM 向けにも提供されている ネイティブ Lisp プロジェクトで、プログラミング言語オタクが好みそうな面白い要素がある
第一印象としては、かなり無理のある JavaScript の例だと感じた。
JavaScript 嫌いのかなりの部分は、初期のブラウザ非互換、つらい DOM API、文法上の落とし穴から来ているように思うが、そうしたものは最近の自分の日常には事実上影響していない。
現代の JavaScript、特に TypeScript と妥当な lint ルールを使えば、言語としては十分まともだ。
依然として CJS 対 ESM、サプライチェーンリスク、エコシステムの変動といった問題はあるが、その大半は言語自体の外にある
括弧の対応を取るのがあまりに面倒だからこそ、わざわざエディタ拡張まで作られた、その体験こそが素晴らしいということなのか。
こういうスタイルのプログラミング、つまり 低い文法密度と関数型のやり方を求めるなら、方向を逆にして連結型 (concatenative) 言語へ行くほうがよい
Lisp インタプリタがコンパイル結果としてどうやって 3.3MB にまでなるのか気になる。
これが軽量化なのか?
それでも、使わないコードを含む大きな標準ライブラリを一緒に配布するのは、軽量化とはほど遠いという点には同意する
衝撃! 恐怖!
閉じ括弧を正しい順番で閉じなければならないとは驚きだ
JavaScript のような言語の冗長さにうんざりしている Lisp 愛好家たちも、APL 系の言語を前にすると向きを変え、できるだけ短くすることよりも 明確さと可読性のほうがはるかに重要だと議論し始めることがある。
LispE の作者は APL の基本演算にある程度の愛着があるようだが、あれほど簡潔な記法の言語に触れていながら、Conway の Life を APL、J、K、さらには Q の版よりも、長くて疎で深くネストした s 式の版で好む理由は理解しがたい