HN 日本語サマリー

← 一覧へ戻る
プログラミング

JoltのためのプログラムイメージとポータブルSchemeバックエンド

Program images and portable Scheme back ends for Jolt (yogthos.net)

4 pointsby yogthos0 コメント

要約

Joltに、プログラムの実行状態を保存・復元できる「プログラムイメージ」機能と、Chez以外のScheme処理系でも動作するポータブルなSchemeレイヤーが追加されました。プログラムイメージは、デバッグ時にエラー発生時の状態をそのまま再現して調査することを可能にし、共同作業やセッション状態の永続化にも役立ちます。ポータブルSchemeバックエンドにより、Joltはブラウザ上での実行も可能になりました。

全文翻訳

2026年8月7日 JoltのためのプログラムイメージとポータブルSchemeバックエンド 最近Joltに2つの機能が追加されました。どちらもJoltとホストランタイムとの結合を緩めたことから派生したものです。1つ目は、Common LispやSmalltalkのスタイルでプログラムイメージをシリアライズする機能、2つ目は、Chezランタイムから切り離されたポータブルなSchemeレイヤーを持つことです。これらの機能がJoltに実質的にどのようなメリットをもたらすか見てみましょう。 イメージ:プログラムのブラックボックス Webアプリケーションのような本番システムをサポートしたことがあるなら、良好なロギングの価値を知っているでしょう。通常は、コードのあちこちにステートメントを散りばめ、検索可能な場所にデプロイし、何か問題が発生したときにパンくずとして使用します。システムにエラーが発生した場合、何が起こったかを理解するために、まずそれが何をしていたかを再構築する必要があります。もしロギングが重要な情報を捉えていなかった場合、調査は推測と、エラーを引き起こした状態を再構築しようとする試みに陥ります。これは、深夜3時に本番環境がダウンした場合、特にフラストレーションのたまる経験になり得ます。 ロギングの難点は、それが質問を知る前に質問を推測することを強制することです。各ログ行は、事前に選択されたプログラム状態の投影であり、多くの場合、呼び出しを書いたときに重要だと思われた2つか3つの要素で構成されています。実際の問題が4つ目の要素にあった場合、ログは何も価値のある情報を提供しません。値は失われており、たとえメモリ内にまだあったとしてもアクセスする方法がないため、後戻りして別の質問をすることはできません。あなたが持っているのは、コードを書いたときに最初に決めたレンダリングだけです。 その結果、人々は防御的に過剰にログを記録する傾向があり、それは別の種類の問題を生み出します。ログの量が多くなり、ノイズが増え、トレースが困難になる一方で、実際に重要だったフィールドが欠けていることがよくあります。 しかし、もっと良い方法があり、ロギングに全く依存する必要がないと言ったらどうでしょうか?これはまさにjolt.imageが私たちにさせてくれることです。事前に何を記録するかを選択する代わりに、エラー発生時のプログラムの全状態をディスクにダンプするだけで済みます。その後、そのファイルをローカルマシンにコピーし、REPLで状態をロードして、何が起こったのかを調べることができます。 これが実際にはどのように見えるかを見てみましょう。エラー発生時に状態をダンプするには、例外ハンドラでimage/dump!を呼び出すだけです。 (try (process-batch! batch) (catch Exception e (image/dump! (str "crash-" (random-uuid) ".jimg") {:error (Throwable->map e) :batch batch :pending @work-queue}) (throw e))) あるいは、列挙を完全にスキップして、プログラム全体を取得します。 (catch Exception e (image/dump-world! "crash.jimg") (throw e)) dump-world!はvarテーブルをウォークし、すべてのデータvarのルートを書き出すため、コード内の何も事前に状態が何であるかを宣言する必要がありません。このイメージはアーキテクチャに依存しないため、arm64サーバーで書き出されたイメージはx86-64デスクトップでも問題なく復元できます。ファイルをローカルマシンにコピーしたら、REPLで開くだけです。 $ jolt repl user=> (require '[jolt.image :as image]) user=> (image/restore-world! "crash.jimg") 412 user=> (filter #(nil? (:price %)) @app.core/current-batch) ({:id 4182, :sku "B-77", ...}) 返ってくるのは、問題が発生した時点でメモリ内に存在した値です。マップ、レコード、サイクル、共有構造は、それぞれのメタデータが付いた状態でそのまま保持され、関数も呼び出し可能な状態で戻ってきます。名前付き関数はライブなものに解決され、匿名クロージャはそのソースフォームとキャプチャされた値を伴って移動し、復元時に再コンパイルされます。そのため、REPLで失敗した関数を、失敗したデータに対して実際に呼び出し、何が間違っていたのかを正確に確認できます。事前にそれらのどれを知る必要もなく、ログファイルよりも厳密に大きい範囲の質問をできるようになりました。 プログラムイメージを航空機のブラックボックスレコーダーと考えることができます。航空機が調査者が後で何を見るべきかを決定できるように計器の状態を保存するように、プログラムイメージは問題のデバッグに必要なすべての情報を提供します。 この時点で、鋭い読者は、ソケットやファイルポートのようなシリアライズできないオープンリソースをこのアプローチがどのように処理するかを尋ねるかもしれません。私が採用したアプローチは、デフォルトでそれらをスタブレコードとして書き出すことでした。イメージが復元されたら、(image/stubs)を呼び出してリストアップし、それらを再オープンするリゾルバを登録するか、(image/register-stub-resolver! kind-or-pred f)を使用してREPLから手動でライブ値にスワップインすることができます。 1つの制限は、コンパイル時定数のクロージャは、定数がコンパイル済みコードに折りたたまれ、保存されたソースがそれを必要とする間は回復できないため、ダンプを拒否することです。partialやcompによって構築されたクロージャも、関連する理由で同じ問題を抱えています。イメージのヘッダーも読み取り時にチェックされるため、互換性のないビルドからの古いデータを受け取ることはありません。dump-world!は可能な限りすべてをダンプしますが、dump!はデフォルトで厳密であり、微妙に不完全なものを書き出すのではなく、問題のあるオブジェクトへのパスを指定します。これは、完全な状態が利用可能であることを明示したい場合に役立ちます。 プログラムを誰かに送る ポストモーテムデバッグは明らかなユースケースですが、もう1つの興味深い応用は、コラボレーションを促進することです。イメージは、他の人に渡すことができるプログラムの状態であり、いくつかの興味深い可能性を開きます。 教育目的では、構築やシードなしで、実際のデータがロードされた実行中のシステムの真っ只中に人を直接置くことができることを意味します。「パイプラインが途中で実装されたイメージがあるから、ステージ2を見てみて」と言うことができると想像してください。バグレポートの場合、同僚はあなたの状態をロードするだけで、あなたの問題を正確に再現できることを意味します。彼らはそれを調べ、何かを変更し、再びダンプして、あなたに送り返すことができます。 これは、伝統的にソースコードが私たちがやり取りする成果物として扱われてきたこととは全く異なるプログラムとの関係です。Joltは、SmalltalkやLispマシンがどのように機能したか、そしてCommon Lispでsave-lisp-and-dieが今日どのように機能するかにより近い方向へ針を動かします。プログラムは、実行できるレシピではなく、保持できる生きたものなのです。 永続的なセッション状態 デスクトップアプリのようなユースケースでは、ユーザーがセッションを保存し、アプリを再起動したときに中断したところから再開できるという興味深い応用もあります。私は、それが実際にはどのように見えるかを示すTodoMVCの例を構築しました。クリックしたり、タスクを追加・変更したりしてから、状態をダンプして、新しいインスタンスで再ロードすることができます。 ポータブルSchemeバックエンド 当初、Joltの目標はChez SchemeでのClojure実装を構築することでした。しかし、Jack Rusherは、ポータブルなランタイムとコンパイラを切り出すことが可能であり、Chezを複数のターゲットの1つにするだけだと指摘しました。Gambitは2番目のホストの明白な選択肢でした。なぜなら、JavaScriptバックエンドを持っており、ブラウザでJoltを実行できるからです。公式サイトを訪れると、現在、ブラウザで実行されているJoltを試すことができるインタラクティブなREPLで遊ぶことができます。 リファクタリングにより、コンパイラのScheme部分は3つの異なるレイヤーに分割されました。コアはポータブルSchemeで書かれており、コレクション、シーケンス、リーダー、プリンター、vars、マルチメソッドを実装しています。これは、真面目な実装であればどれでも変更なしで実行できる通常のSchemeです。 次に、ホストが透過的に見えるアダプター契約があります。すべてのホスト機能はsa-*エントリポイントを経由し、契約ファイルにはティアにグループ化された72の名前がリストされています:システム(クロック、環境、終了)、スレッド、eval、イントロスペクト(バックトレース用の継続フレーム)、ffi、ネイティブコンパイル、イメージ。ターゲットはティアを実装するか、正直にそれを低下させることができます。これにより、存在しない機能はメッセージを運ぶエラーを発生させるか、空を返します。