すべてのプログラミング言語には正式な文法がある。コンパイラはそれを読み取り、parse tree を構築し、合わないものをすべて拒否する。エディタは文法を完全に無視して、ユーザーが何でも入力できるようにしている。

その乖離は、驚くほど多くの摩擦の源だ。Autocomplete は文脈的に意味をなさない identifier を提案する。Syntax highlighting はリファクタリングの途中で壊れる。エディタがユーザーが犯した間違いを目撃しているのに “Unexpected token” エラーを出す。私たちはこれを普通のことと考えている。なぜなら、テキストエディタは40年間ずっと唯一のものだったからだ。

テキストは中間表現だ。コンパイラは parse した瞬間にそれを捨てる。なのに、なぜ私たちはテキストを編集しているのだろう?

構造化編集が実際に意味するもの

構造化編集とは、ソース・オブ・トゥルースが文字列バッファではなく Abstract Syntax Tree であることを意味する。エディタはツリーをテキストとしてレンダリングしてユーザーの便宜を図るが、すべての操作はノードを直接操作する。FunctionDeclaration。BinaryExpression。IfStatement。これらが原子だ。

パラメータを追加したい?カーソルを括弧の間に置いて入力するのではなく、FunctionDeclaration ノードに対して “add parameter” 操作を呼び出す。エディタはプレースホルダー付きの新しい Parameter ノードを作成する。レンダリングされたテキストが更新される。括弧のバランスを崩すことは決してない。なぜなら、括弧は入力する文字ではなく、レンダラーが生成する視覚的境界だからだ。

これは空想ではない。Lisp は1960年代からこのように動作している。なぜなら S-expressions は定義上ツリーだからだ。Smalltalk はメソッドを grep 可能なテキストファイルではなく、ブラウザ内で選択可能なオブジェクトとして扱った。近年では、Unison 言語がコードを AST ノードの Merkle tree として保存し、JetBrains MPS は projectional editor であり、文法が言語と編集体験の両方を定義している。

構造的安全性の保証

ツリーエディタがリファクタリング時に行うことを示そう。次の Python スクリプトはその内部ロジックを近似している:

import ast

# The canonical representation is a tree, not text
tree = ast.parse("result = calculate(x, y)")

# Find the Call node and append a keyword argument
for node in ast.walk(tree):
    if isinstance(node, ast.Call):
        node.keywords.append(
            ast.keyword(arg='debug', value=ast.Constant(value=True))
        )

# Render to text only when needed (Python 3.9+)
print(ast.unparse(tree))
# result = calculate(x, y, debug=True)

この操作は構造的に安全だ。calculate(x, y debug=True) を誤って生成することはできない。なぜなら keywordskeyword ノードの typed list であり、string buffer ではないからだ。レンダラーがカンマと等号を挿入する。ツリーは常に構文的に有効だ。

これはほとんどの formatter や linter の動作方法でもある。Black、prettier、rustfmt はすべて AST に parse し、ツリーを変換して出力する。違いは、それらがテキストを読み込み、変換し、テキストを書き出すという点だ。ツリーエディタはテキストの往復を省略する。

なぜ誰もツリーエディタを使わないのか

これがそんなに優れているなら、なぜプロの開発者はまだ VS Code、Vim、Emacs を使っているのだろう?

第一の理由はインタラクションの速さだ。テキスト編集はタイトなループを最適化している:変更を考える → 指で文字を入力する → 目で確認する。すべてのキー入力は同じ操作だ。ツリーエディタでは “insert an if statement”、“rename a variable”、“extract a method” はすべて異なるジェスチャーだ。キーを乱打することはできない。認知的オーバーヘッドが積み重なる。

第二の理由はエコシステムの壁だ。パイプラインのすべてのツールはテキストを前提としている。git diffgrep、GitHub code review、コード検索、インターネット上のすべてのチュートリアル。構造化フォーマットは存在するが、ソースコードにおいて生のテキストを駆逐したものはない。ネットワーク効果は絶対的だ。

第三の理由は視覚的レンダリングの問題だ。プログラマーは数十年の組版慣行を持つテキストとしてコードを読む。コメントがノードの半分を説明するとき、それはどこに置かれるのか?ユーザーが望むものを指定し終わっていない途中で、ツリーをどう表示するのか?ツリーを読み書き可能なテキストとしてレンダリングすることは、テキストをツリーとしてレンダリングするより難しい。

テキストエディタにおける構造的操作

エディタを変えなくても、ツリー認識の利点を得ることができる。すでにいくつかのツールは AST 上で動作しつつテキストインターフェースを提示している。

Lisp 用の Paredit や Smartparens は S-expressions をツリーとして扱う。式を親スコープに “slurp” したり、“barf” で吐き出したりする。括弧はテキストのままだが、編集コマンドはツリー操作だ。構文エラーを一切起こさずに subtree 全体を移動できる。

モダンな IDE のリファクタリングはコードを parse し AST を構築し、どの変数がパラメータになるかを判断してツリーを書き換える。IntelliJ で “extract method” を実行するとき、それは text replacement をしているわけではない。構造的変換を実行し、結果を出力しているのだ。

Language server はすべての操作のために内部的に AST を構築する。Go-to-definition、rename、inline hints はすべてツリーを走査する。プロトコルは互換性のためにテキスト位置をやり取りするが、知性は構造的だ。

エディタを変えずに実験する方法

コードの書き方とコンパイラの理解の間のギャップを埋めたいなら、ここから始めよう:

  1. Python の ast モジュールを使う。 実際のコードを parse し、ast.walk でツリーを走査して変換するスクリプトを書く。ほとんどの Python ツール(flake8、mypy、Black)はまさにこれをやっている。

  2. Tree-sitter を探索する。 オンラインプレイグラウンドでファイルを parse し、concrete syntax tree を調べる。パーサーが実際に何を見ているか確認する。

  3. Lisp の構造化編集を試す。 Clojure や Emacs Lisp を書くなら、Paredit をインストールしよう。1日だけ文字ではなく expression 単位で移動することを自分に課す。その遅さが教訓になる。

テキストファイルは消えない。ツールが多すぎるし、歴史が深すぎるし、muscle memory が強すぎる。しかし、テキスト編集と構造的な理解の間のギャップは、バグや摩擦、認知的負荷の実在する源だ。それを解決するためにツリーエディタは必要ない。必要なのは、テキストが本物だと装うのをやめることだ。