Next
No newer accessible resource.
2026-08-27 22:46
wallet of satoshi に入れているsatsをセルフカストディにしなきゃらしい?リカバリフレーズなくしそうで怖いけど、まあ、しかたないか。
Next
No newer accessible resource.
2026-09-01 12:14
lkjscriptは新しいプログラミング言語ですが、よくある新言語とは少し着眼点が異なります。
新しいプログラミング言語というと、構文や型システムが話題になりがちです。たとえばRustの所有権、Haskellの遅延評価、LispのS式などが挙げられます。しかし、lkjscriptで最初に見るべきポイントは構文ではありません。
lkjscriptの最も根本的な特徴は、プログラムの正本(authoritative source)がソースコード(テキスト)ではないという点にあります。この設計方針から、言語全体の様々な仕組みが導かれています。
一般的なコンパイラでは、おおまかに次のようなパイプラインで処理を行います。
source code
↓
parser
↓
AST
↓
IR
↓
machine code
このモデルでは、ASTやIRはソースコードからいつでも再生成できる派生物にすぎません。そのため、プログラムの「正」となる情報は常にソースファイルの中にあります。
lkjscriptでは、この関係が逆転しています。
authoring request
↓
typed meaning graph
↓
compiler
↓
artifact
システムの中心にあるのは、typed meaning graph と呼ばれるグラフ構造です。モジュール、型、関数、フィールド、依存関係、テスト、実行ターゲットといった要素が、すべて型付きのオブジェクトとしてこのグラフに格納されています。
ソーステキストや変更要求、インデックス、コンパイラキャッシュ、実行バイナリなどは、独立した別のプログラム表現ではなく、このグラフを編集・利用するためのビューや派生物にすぎません。
これは単に「パース済みのASTをディスクに保存した」という話とも異なります。
たとえば、ある関数の名前を foo から bar に変更したとします。通常のソースコードでは、名前は関数を参照・解決するための重要な識別子です。一方、lkjscriptにおいて名前は人間やツールが対象を見つけるための手がかり(locator)として扱われます。
実体の同一性は、以下のような型付きの安定したIDによって管理されます。
mod_...
decl_...
type_...
そのため、関数名を変更しても「その関数自体の同一性」が変わるわけではありません。
プログラムを単なる巨大なテキストファイルとしてではなく、永続化された意味オブジェクトの集合として扱う点が、この設計の基本になっています。
プログラムがグラフとして保持されているため、編集方法も一般的なテキストエディタのそれとは異なります。
lkjscriptに対する変更は、たとえば次のような操作として送信します。
request base=rev_...
create.module as=$notes name=notes
create.record as=$note module=$notes name=Note visibility=public
add.field as=$text record=$note name=text type=text
これは「特定の行の文字列を置換する」というパッチではなく、「指定したリビジョンをベースにモジュールを作成し、その中にレコードを作り、フィールドを追加する」というセマンティックな操作(意味的な編集)の要求です。
さらに、この変更処理は以下の2段階で行われます。
change plan
change apply
plan の段階で変更要求を正規化し、IDの割り当て、影響範囲の調査、整合性の検証、実行すべきテストの特定などを行います。この検証結果に対して plan_... というトークンが発行されます。
続く apply では、ベースとなるリビジョンが変更されていないこと、レビュー済みの論理的な効果と一致していることを確認した上で、変更を公開(コミット)します。
これはデータベースのトランザクションと非常によく似ています。
内部の保存構造も同様の発想で作られており、新しいセマンティックオブジェクトをイミュータブルな形式で書き出した後、最後に HEAD の参照をアトミックに切り替えます。これにより、編集途中の不整合な状態が外部から見えるのを防いでいます。
つまり、
edit text
→ parse
→ compile
という従来のサイクルではなく、
read revision
→ propose semantic transaction
→ validate
→ commit new revision
という、データベースに近いライフサイクルを持っています。
この「何が正本(authority)か」という分離は、コンパイラの設計にも一貫して適用されています。
lkjscriptはインクリメンタルコンパイル用のキャッシュを持っていますが、キャッシュはあくまで派生データ(derived state)にすぎません。
たとえば変更を受け入れた直後にインクリメンタルコンパイルが失敗したとしても、
accepted semantic revision
までロールバックされることはありません。プログラムの変更自体はすでに確定しているためです。
コンパイル時にキャッシュの整合性が疑わしい場合は、単にキャッシュを破棄してフルビルドで作り直せば済みます。
lkjscriptにおけるデータの位置づけを整理すると、次のようになります。
meaning graph authoritative(正本)
compiler cache disposable(破棄可能)
artifact derived(派生物)
deployment separate authority(独立した権威)
runtime handle disposable(破棄可能)
一般的な開発環境でも「ビルドディレクトリは消去して構わない」という運用は行われますが、lkjscriptではその境界がシステムのデータモデルとして厳密に定義されています。何が失われるとプログラムそのものが壊れ、何が再生成可能なのかが明確に区別されています。
プログラムがグラフ構造として保持されているため、コードの探索もグラフのトラバーサルとして実行できます。
lkjscriptには、通常の inspect に加えて query context というクエリインターフェースが用意されています。
lkjscript --project ./hello query context mod_... \
--direction both --depth 2 --limit 20 --bytes 65536
このコマンドを実行すると、特定のセマンティックオブジェクトを起点にして、指定した深さまでの周辺グラフを取得できます。
単に検索用APIが用意されているだけでなく、検索対象が特定のリビジョンに固定(pin)されている点が特徴的です。ページネーション用のコンティニュエーション(続きの取得)も、リポジトリ、リビジョン、起点ノード、探索方向、深さ、ソート順などに紐づいています。
また、サーバー側で検索セッションの状態を保持しません。
revision
+ root
+ direction
+ depth
+ continuation
これらがあれば、イミュータブルな正本からいつでも全く同じ論理結果を再現できます。
これは人間向けのIDE機能として有用なだけでなく、静的解析ツールやLLMなどの機械がコードを読み込む際にも便利です。数千行のソースファイルを丸ごと読み込ませる代わりに、「この関数の周囲2ホップ分の依存関係を取得する」といったピンポイントな読み込みが可能になります。
言語処理系を設計する際、どこまでをその言語自身で書き、どこからを処理系(ホスト言語)側の組み込み機能(magic / intrinsics)とするかは常に問題になります。
lkjscriptの処理系自体はRustで書かれていますが、標準ライブラリの処理を安易にRustの組み込み関数として実装することは避けています。
わかりやすい例が fold 関数です。
list-fold-left<Item, State>(
List<Item>,
State,
Function(State, Item) -> State
) -> State
これは標準パッケージの meaning graph 内で実装されています。内部では通常の再帰、list-length、list-get、および関数呼び出しのみを用いて記述されており、fold 専用のVM命令やRustへのコールバックなどは存在しません。
これにより、
言語自身で表現できる領域
と
処理系でしか実現できないプリミティブな領域
の境界が綺麗に保たれています。
標準ライブラリ自体も通常の meaning graph パッケージとして定義されているため、ユーザーのコードと全く同じコンパイラ、バリデータ、VM、リファレンスインタプリタを通過します。
処理系開発では、パフォーマンスや利便性を理由にホスト側の組み込み関数を増やしてしまいがちですが、lkjscriptではそのアプローチを自覚的に制限しています。
実行系のアーキテクチャにも興味深い特徴があります。
lkjscriptにはバイトコードを実行するVMが存在しますが、それとは別にリファレンスインタプリタも用意されています。
副作用のない純粋なコマンドやテストの実行時、処理系は以下の2つの経路で並行して実行を行います。
meaning graph
├─ compiler → bytecode VM
└─ reference interpreter
もし両者の実行結果が一致しなければ、テストは失敗とみなされます。つまり、リファレンスインタプリタがコンパイラとVMに対するセマンティックオラクル(意味論の正しさを保証する判定器)として機能します。
これはコンパイラの検証において非常に有効です。
たとえば、
2 + 3
という式をコンパイルしてVMで実行し、5 が返ってきたとしても、それだけでコンパイラが正しいとは断言できません。コンパイラとVMが同じバグを抱えていて、たまたま結果が一致している可能性があるためです。
完全に独立して実装されたインタプリタでも同じ 5 という結果が得られるのであれば、2つの独立した実装が同じセマンティクスを満たしていることの強い根拠になります。
なお、HTTPリクエストの送受信やデータベースへの書き込みといった副作用を2重に実行するわけにはいかないため、この差分実行(differential execution)は純粋な処理に限定して適用されます。
Webアプリケーションを扱うようになると、コードベースには様々な外部環境の設定が混入しがちです。
たとえば、次のような要素です。
listen address
database path
TLS certificate
remote endpoint
secret
timeout
lkjscriptでは、これらの環境依存の情報を meaning graph に含めない設計になっています。
たとえばHTTPアプリケーションの場合、グラフ(プログラム本体)側には以下のような論理構造のみを定義します。
route
handler
request/response types
component
requirement
port
target
一方、具体的な接続先やリソース上限などはデプロイメント記述子(deployment descriptor)側に分離して配置します。
127.0.0.1:8080
/data/example
https://example.com/
TLS trust root
resource limits
さらに、ステートフルなアプリケーションが保持するデータも、セマンティックリポジトリとは明確に区別された運用データ(operational data authority)として扱われます。
概念としては、以下の3つの領域が分離されています。
program meaning
│
├── artifact
│
deployment authority
│
operational data
アプリケーションがデータを更新してもプログラムのセマンティックリビジョンは変化しませんし、デプロイ先のアドレスを変更してもプログラム自体を書き換えたことにはなりません。
当たり前の区別に思えますが、既存の多くのフレームワークではこの境界が曖昧になりがちです。lkjscriptでは、これらを「権威(authority)の違い」として明確にモデル化しています。
外部世界とのやり取りに関しても、この分離の思想に基づき capability として厳密に管理されます。
たとえばアウトバウンドのHTTP通信を行う場合でも、プログラム側から任意のURLへ自由に接続できるわけではありません。
プログラム(グラフ)側は HttpClient.get という能力の要求(requirement)を宣言し、デプロイメント側が具体的な接続先エンドポイントやTLS証明書に対する権限を付与(grant)します。
つまり、
program:
HTTPクライアントの機能が必要である(要求)
deployment:
この特定のエンドポイントに対するアクセス権限を与える(付与)
という関係になります。
これは依存性の注入(DI)に似ていますが、単にテストを容易にするための抽象化ではなく、外部の権威をプログラムの意味論から完全に切り離すための境界として機能しています。
現在用意されているHTTPSクライアントの実装も意図的に最小限に絞られており、リダイレクトやリトライ、プロキシ、WebSocket、任意URLへのアクセスなどは提供されていません。機能が不足しているというよりは、プログラムに許可された能力を可能な限り正確かつ安全に定義しようとした結果です。
現在の公開版lkjscriptは、x86_64-unknown-linux-musl 向けの静的リンクバイナリとして配布されています。
この単一の実行ファイルだけで、プロジェクトの作成からビルド、実行、サーバーの立ち上げまで、一連のライフサイクルを完結して行えます。
new
status
inspect
query
change
check
build
run
serve
worker
data
package builtin
標準パッケージやプロジェクトテンプレートもバイナリ内に埋め込まれているため、外部のリポジトリをクローンしたり、Cargoなどのツールを別途用意したりすることなく、以下のような手順で即座に動作確認が可能です。
./lkjscript new ./site --template http --name site
./lkjscript --project ./site check
./lkjscript --project ./site build --output ./site/generated/application.lkja
./lkjscript serve --deployment ./site/service.deployment.json
これは単に「コンパイラを静的リンクして配布した」というレベルにとどまりません。コンパイラ、パッケージマネージャ、プロジェクトテンプレート、アーティファクトローダー、VM、HTTPランタイム、データストアなどが、すべて単一の配布物として統合されています。
そのためlkjscriptは、単なるプログラミング言語というよりは、公式リポジトリの表現どおり programming language and application platform と呼ぶほうが正確です。
開発初期の実験的プロジェクトとしての面白さとして、後方互換性を思い切って切り捨てる方針をとっている点も挙げられます。
lkjscriptは、古いリポジトリフォーマットを長期間にわたってサポートし続けるような設計にはなっていません。古くなった(obsoleteな)グラフリポジトリは明快に拒否されます。
フォールバック用の古いパーサー、レガシーモード、移行用マイグレーションコマンド、新旧形式への二重書き込みといった互換性のための仕組みは、現在の公開アーキテクチャには含まれていません。
商用言語でこのような割り切りを行うとユーザーの負担になりますが、アーキテクチャの理想形を急速に模索している段階の言語にとっては大きな利点があります。
古いモデル、新しいモデル、その間の変換レイヤーなどを同時にメンテナンスし続ける必要がないため、アーキテクチャを刷新した際には古いコードパスを完全に削除できます。これはlkjscriptのコードベース全体を通して一貫した方針です。
new model
old model
old-old model
conversion
compatibility layer
こうした歴史的経緯の積み重ねを抱え込まずに済むことが、開発速度の維持につながっています。
個々の要素を分解して見ていくと、使われている要素技術自体が前代未聞というわけではありません。
イミュータブルなオブジェクトストレージ、リビジョン管理、中間表現(IR)の永続化、ケーパビリティベースのセキュリティ、リファレンスインタプリタとの差分実行といった技術は、それぞれ先行事例が存在します。
lkjscriptの特異さは、それらの技術を以下の単一の前提から徹底的に論理展開して組み上げている点にあります。
プログラムとはソースコードではなく、型付けされた意味そのものである
この前提を起点に置くことで、
といった設計判断が、必然的な帰結として一つに繋がります。
lkjscriptの面白さは、斬新な構文を考案したことにあるのではなく、普通の言語処理系が暗黙に置いている前提を、
program = source files
から、
program = accepted typed semantic graph
へと置き換え、「では言語処理系と開発環境全体を作り直すとどうなるか」をゼロベースで真摯に実験している点にあります。
その結果として、パーサーだけでなく、エディタ、バージョン管理、コンパイラ、パッケージ管理、ビルドシステム、ランタイム、デプロイ、さらにはデータベースとの境界に至るまでが再定義されています。
なお、現在の実装は完成された汎用プラットフォームというわけではありません。JIT/AOTコンパイル、安全でないコードを実行するためのサンドボックス、マルチテナント分離、アーティファクトの署名検証、分散合意形成、暗号化ストレージなどはまだ実装されていません。
現時点のlkjscriptは、多種多様な機能を広く浅く提供するのではなく、厳選された少数の機能に対して「権威(authority)とセマンティクスが矛盾なく閉じている状態」を徹底的に追求した実験的言語と見るのが自然でしょう。