ハクソク

世界を動かす技術を、日本語で。

エージェント時代におけるリテラテプログラミングの再考が必要です

概要

  • Literate programmingは、コードと説明文を組み合わせて理解しやすくする手法。
  • 実際には、コードと説明文の二重管理が負担となり普及が限定的。
  • Emacs Org ModeやJupyter Notebookが代表的な実践例。
  • LLM(大規模言語モデル)エージェントの登場で、この負担が大幅に軽減。
  • 今後は、物語のように読めるコードベース実現の可能性。

リテラテプログラミングの現状と課題

  • Literate programmingは、コードと説明文を並列に記述し、初心者でも理解できる物語的なコードベースの実現を目指す手法。
  • 実際には、コード本体と説明文の2つのストーリーを維持する手間が大きく、普及が限定的。
  • 歴史的には、Jupyter Notebookがデータサイエンス分野で広く使われており、計算・結果・説明が同じ画面に表示される構成。
  • Emacs Org Modeのorg-babelパッケージは、多言語対応のリテラテプログラミングをサポートし、任意の言語を実行し結果を文書に自動挿入可能。
  • しかし、Orgファイルを「ソース・オブ・トゥルース」として使うと、編集ごとに「tangle(抽出)」作業が必要となり、実運用では煩雑さが課題

個人利用での実践例とメリット

  • 個人設定や手順書の管理にはリテラテプログラミングが有効で、コマンドライン作業をOrgファイル内でコマンドとして記述・実行・編集し、手順書と実行履歴を同時に作成。
  • 手順書作成と実行が一体化することで、追加のメモ作成が不要となる利便性。
  • LLMエージェント(Claude、Kimiなど)はOrg Mode構文を高精度で扱え、説明文とコードの同期やtangle処理を自動化可能。

LLMエージェントによる革新

  • テストや機能検証時、エージェントにOrg形式のランブック作成を依頼し、説明文で意図を確認、コードブロックは対話的に実行可能。
  • 説明文とコードの同時編集や自動反映が可能となり、二重管理の負担が消滅。
  • AGENTS.mdファイルで、エージェントに「Orgファイルを唯一の正とし、説明文を必ず記述し、実行前にtangleする」などの運用ルールを指示可能。
  • エージェントは何度でも説明文の修正・再生成に対応し、リテラテプログラミングの最大の障壁だった追加労力を排除
  • コードベースを多様な形式にエクスポートでき、読むことが主業務となるエンジニアにも有用

Org ModeとMarkdownの比較・今後の展望

  • Org ModeのEmacs依存が普及の障壁であり、より汎用的なMarkdownへの移行も検討されるが、Markdownにはメタデータ記述機能が不足
  • Org Modeはプロパティやヘッダー引数で、コードブロックの実行先や詳細設定など、プログラム的な制御が可能。
  • 過去はEmacs Lispで独自機能を組み込むことで柔軟性を確保していたが、今はLLMが自動で必要なLispコードを生成可能。
  • **「物語のように読めるコードベース」**という理想の実現に、エージェントの力で現実味が増している。
  • 大規模コードベースでの本格運用は未経験だが、手順書やテスト用途では高い効果を実感。

結論と今後の課題

  • リテラテプログラミングの普及を阻んでいた労力が、LLMエージェントにより解消
  • コードと説明文の同期や抽出・実行が自動化され、コードの可読性や品質向上にも寄与する可能性。
  • 今後は、エンジニアの主な役割が「読むこと」へ移行する中で、物語的なコードベースの重要性が増すと予測。
  • Org Modeの枠を超えた、より汎用的なドキュメント形式の模索と、大規模プロジェクトでの実践検証が今後の課題。

Hackerたちの意見

LLMは言語モデルだから、書かれた言葉の明確さに投資するのはめちゃくちゃ効果的だよね。「リテラルプログラミング」が必須かどうかは分からないけど、良い名前やドキュメンテーション、型の署名、戦略的なコメント(「なぜ」について)、良いREADME、考え抜かれた抽象化があれば、しっかりしたパターンが確立できると思う。フルで「リテラルプログラミング」をやる必要はないかも。コミュニケーションに焦点を当てるっていうのがいいかもね。ノートブックや例、スクリプトなんかがパターンを強化するのに役立つよ。結局のところ、それが大事なんだよね:人間の読者とLLMの両方が従うべきパターンを確立すること。
そうだね、必要なのはdocstringと戦略的コメント、そしてリテラルプログラミングの中間くらいだと思う。基本的に、コードの高レベルな構造をドキュメント化するのはすごく役立つ。ファイルレベルやサブディレクトリレベル、プロジェクトレベルでの広範なdocstringみたいな感じ。問題は、主要なアーキテクチャの概念や決定がファイルやディレクトリをまたいでいることが多いから、必ずしもそこが正しい場所じゃないってこと。それに、コードファイルに何が適切か、デザインドキュメントに何が適切か、どうやってそれらを同期させるかっていう問題もある。
ノートブックはリテラルプログラミングの一例だよ。
最近、いくつかのプラクティス(ちゃんとしたREADMEやアーキテクチャを書くこと、言語を正確かつ明確に使うこと、文脈を提供すること、リテラルプログラミング)が人間を助けるために作られたのに、広く採用されていない傾向に気づいたよ。理由は「手間がかかりすぎる」っていうこと。でも、LLMを助けるためにやると、急にみんなやる気になるみたい。
プログラミングの経験から言うと、人間は具体的な質問が出るまではドキュメントをちらっと見るだけだよね。それで、答えを探すよりも他の人に聞くことが多い。最大の問題は、人間は必要になるまでドキュメントが必要だって気づかないこと。あるプロジェクトでは、docblockスタイルのコメントをたくさん使ってたんだけど、どのファイルを開いても自然言語か注釈のどちらかに少なくとも一つのエラーがあったよ。もしLLMが実行するすべてのタスクでドキュメントを実際に使っているなら、もしくはそれなしでは十分な出力ができないなら、それが日常の仕事でドキュメントを書くための動機付けになると思う。
ドキュメントはコードよりも早く腐るよね。コードが動くためには正確である必要はないし、コメント(特にデザインドキュメント)を無視して、直接コードに行く方がいいことが多い。
何年も前に盗んだ観察を言い換えると、私たちの中の何人かは、コンピュータと話すことを学べば人間と話すことを学ぶ必要がなくなると思ってたんだ。だから、彼らは感情的な成長の最も重要な4年間をそれに費やして、卒業したらその分野で他の誰よりも遅れていることに気づいたんだ。
LLMが自分のインラインドキュメントを積極的に修正してくれたことがあって、すごく嬉しいサプライズだった。「コメントが古くなっていて、実際の実装を反映していないことに気づきました。修正してもいいですか?」って聞いてきたんだ。
もしかしたら、そういう人たちがプログラマーを管理していて、自分でコードを書いていなかったら、似たようなことをしていたかもしれないね。
違うのは、彼らがそのREADMEやアーキテクチャ、その他のドキュメントを書くのにLLMを使ってるってこと。努力をしてないんだよね。
テストコードと本番コードを対称的に持つのは、たくさんの利点があるよ。ダブルエントリー会計みたいなもので、コードの動作をコード自体から見ることもできるし、それが本当にやっていることを証明するコードからも見られる。テストや本番コードのどちらかを変えることでコードを変更できて、もう一方がそれに従う形になる。コードレビューも楽になるし、本番コードに混乱したらテストコードが説明を持ってることが多いから、逆も然り。必要に応じて切り替えればいいんだ。たくさんの利点があるけど、もちろん余分なコードが増えるのがデメリットかな。読みやすさの向上がそれに見合うかはあなた次第。
ここ10年くらい、ほぼ全てのコーディングでリテラルプログラミングを使ってきたよ。nbdevを作ったおかげで、ノートブックを使ってソフトウェアを書いたり、ドキュメント化したり、テストしたりできるようになった。ここ数年で、LLMをノートブックやnbdevと統合してSolveitを作ったんだけど、会社のみんながほぼ全ての仕事で使ってる(弁護士や人事もね)。リテラルプログラミングはプログラミングだけじゃなくて、もっと色々なことに役立つって分かったよ!
面白いし、ちょっと関連したアイデアなんだけど、LLMを使ってコメントやドキュメントがコードとズレてる時に教えてくれる仕組みを作るのはどうかな。ドキュメントの大きな問題は、書いた時は正確でも、コードと比べると時間が経つにつれて古くなっちゃうことだよね。コンパイラは型や実装がズレてるかどうか教えてくれるけど、今まで自動でコメントがまだ正しいかどうかをチェックするものはなかった。これ、スタートアップにできそうじゃない?
もうすでにこのことをやってるスタートアップが一つあるよ(私はその会社とは関係ないけど): https://promptless.ai/
CIがAIに接続されてるなら、SLMを使って定期的にチェックすることもできるよ。例えば、https://github.github.com/gh-aw/ や https://www.continue.dev/ みたいな感じで。アーキテクチャのズレを検出することもできるかもね。
以前、すごいアイデアを思いついたことがあって、自動化されたドキュメンテーション駆動のパラダイムを作るってやつ。各ディレクトリやモジュール、クラス、関数には必ずDocStringやJSDocが必要で、上位のもの(ディレクトリやモジュール)は機能やアーキテクチャのドキュメントになるって感じ。チケットは誰かがドキュメントの変更を提案するPRを開くところから始まって、PMやテスターみたいな非技術者でもできるようにする。PRは開発者に渡されて、ドキュメントの変更に合わせてコードを修正する。マージする前に、ツールが変更されたコードの横にドキュメントを表示して、レビュアーはそれがまだ有効かどうかを確認するためにチェックボックスを明示的にチェックしなきゃいけない。さらに、ドキュメント同士をリンクさせることができるから、他のコードが自分の作業にどう関係してるかを見つけたり、そのドキュメントを読んで全体の文脈や注意点を理解できるようになる。
私はテクニカルライターなんだけど、思いつく限りでも2023年以降にこの分野で少なくとも10のスタートアップが立ち上がったと思う。
コードが何をしてるかAIに聞けばいいのに、なんでコメントが必要なの?
ライティングプログラミングの軽いバージョンが、APIが小さくて慣習が重視される言語と組み合わさって、エージェントプログラミングの時代に栄えると思う。軽いAPIは多くのボイラープレートコードを意味するかもしれないけど、こういうモデルはボイラープレートを生成するのが得意だからね。最近はPythonやTypeScriptみたいな動的言語よりもGoを使うことが多い。エージェントがプログラムを書くなら、速い言語で書いた方がいいから。速いコンパイルはエージェントがデザインを素早く反復して実行できるようにするし、また戻ってこれる。Goのエコシステムはスタイルガイドやデザインパターン、標準的なやり方が豊富だから、nilポインタエラーや並行コードの微妙なレースコンディション、コンテキストキャンセルの問題みたいな明らかな落とし穴を防げる。だから人々はパターンに頼りがちで、エージェントはそれをうまく拾える。私のライティングプログラミングのスタイルは、各パッケージに十分なトップレベルのドキュメントがあって、すべての公開APIに良いドキュメント文字列があることを確認すること。コードベースに取り組む前に、エージェントにGoogleのGoスタイルガイドを読むように指示してる。これが意外といい結果をもたらすことが多いよ。[1] https://google.github.io/styleguide/go/
> Goのエコシステムはスタイルガイドやデザインパターン、標準的なやり方に重きを置いてる。GoはRob Pikeの同僚への軽蔑から設計されたから、LLMには適してるみたい。
私は納得できないな。 - 自然言語はあいまいだから、それがプログラミング言語を作った理由でもある。だから、コード周りのドキュメントも一般的にあいまいになりがち。さらに悪いことに、実行されないから、古くなっちゃうこともある(微妙な方法で)。 - LLMは大量のソースコードで訓練されていて、自然言語よりも小さい範囲だと言えるかもしれない。私の経験では、LLMは例えば二つのプログラミング言語間のコードを翻訳するのが得意。でも、私のプロンプトをコードに翻訳するのはうまくいかない。なぜなら、私のプロンプトは自然言語であって、あいまいだから。 - これは「自然言語対プログラミング言語」か「悪いコード対良いコード」の問題なのかな。悪いコードをドキュメント化することでLLMや人間が意図を理解するのに役立つ一方で、良いコードをドキュメント化すると逆にあいまいさが増すこともあると思う。私が学んだのは、私たちは人間が読めるようにコードを書くということ。良いコードは意図を明確に表現するコードだよね。もしコードのあちこちにコメントが必要なら、それはそのコードが本来あるべき良さではないってことだと思う。もちろん、毎年コードの質が悪化しているという意見もあって、それに伴ってドキュメントの必要性が増しているのは確かだ。著者が何をしたかったのか理解するのが難しくなっているからね。
LLMにリテラルプログラミングを生成させることはしてないけど、トレードオフについて話すように頼んでるよ。コメントや説明がたくさんついてるフルの例を持ってるし、スキーマやドキュメントへのリンクも含まれてる。LLMにそれをテンプレートとして使わせると、あそこにある全てを使う必要はないってことが分かるから、ハルシネーションがかなり減るんだよね。
> コードにあちこちコメントをつける必要があるってことは、そのコードが本来あるべきレベルに達してないってことだと思う :-) 良いコードだけで十分なら、ドキュメントじゃなくてソースコードを読むはずだよ。良いソフトウェアには良いドキュメントが必要だと思う。リテラルソースの文章はドキュメント向けであって、実装についての行レベルのコメントじゃないんだよね。
> 自然言語はあいまいだ。それがプログラミング言語を作った理由だよ。だから、コードに関するドキュメントも一般的にあいまいなんだ。さらに悪いことに、実行されないから、古くなってしまうこともある(時には微妙な方法で)。この考え方は本当に嫌いだ。特定のルールでコメントを禁止しているコードベースに出くわしたこともある。確かにコメントは嘘をつくこともあるし、ドキュメントしているコードと常に一致する保証もないけど、コメントがない方が千倍ひどい。コードが何をしているかはいつでもわかるけど、問題はなぜそのようにしたのかなんだ。例えば「このコードはO(n)で動作します。検索されるアイテムはほんの数個しかないからです。O(log2 n)の検索が必要なほどアイテムが増えたら更新してください」ってコメントを入れる。これで未来の開発者に、著者(私)が最も効率的なコードではないことを理解しているけど、読んでいる人には未知のことを考慮に入れるとこれが最適だってことが伝わるんだ。追記: 部族知識は最悪の知識だ。みんなが知っていると仮定されていて、新しい人が入るときに伝えられるけど、実際にはオンボーディングを行う人たちが断片的な情報や誤った仮定を持っていることが多い。子供のゲーム「電話」のように、知識の伝達はいつも災害に終わるんだ。
「でも、私のプロンプトをコードに変換するのはうまくいってない。なぜなら、私のプロンプトは自然言語で、あいまいだから。」それだけじゃなくて、出力の生成方法を全くコントロールできないものに、たくさんのテキストを入力するのは非常にイライラするし、満足感が得られない。入力が同じでも出力が再現できないこともあるしね。自然言語は本当にあいまいで、日々ますますあいまいになっている。「『バイブ』って正確に何を意味するの?」って感じだ。60年前の人々は特定の言い回しをしていて、彼らの意図を解釈する余地がほとんどなかった。今はそうはいかない。
プログラミング言語も自然で曖昧だよね、READって何を意味するの?型を確認するために調べないといけない。力は監査可能なところから来てるけど、コードを書くたびに監査する必要はない。自分が良いコードを書いてると思ってる?コンパイラが通った後にそれを証明してみて。自然言語はアイデアが豊かだから、純粋な自然な説明からコードにするのは、コードからコードにするより難しいかもしれないけど、ただコードを翻訳するだけではあまり得られない。想像力に制限されるのが一方、もう一方はすでに存在していて、ルーチンとして呼び出せる。良いコードの感覚があるのは、自然言語に慣習や共有された意味があるから。プログラミングの目的が人間としてより良くコミュニケーションを学ぶことなら、曖昧さと戦うべきで、逃げるべきじゃない。100年後には、君の慣習が実際には「良いコード」だったなんて誰も理解しないよ。
> だって、私のプロンプトは自然言語だから、曖昧なんだ。法律用語は自然言語が曖昧すぎるから特に発展したんだよね。プロンプトのために同じレベルの具体性があれば、すごく効果的なんだ。コードでコンピュータに指示を出すときの問題の一つは、何かをどうやってやるかを非常に狭く説明してること。だけど、時々、最高の「やり方」を知らないこともある。ただ自分が知ってることしかわからない。自然言語のプロンプトを使うと、AIはそのトレーニング知識を活用して、より良いやり方を考え出せる。もちろん、たくさんの方向性が必要だけど(通常は)、多くの場合、優れた結果が得られることがあるよ。
ドキュメントとコードは相互にエラーを修正し合うコードとして機能する。冗長な情報がなければ、エラー検出や修正の利点は得られないよ。
著者がCUEについて知らないかもしれないけど、今年初めのHNの投稿でCUEを使ったリテラルプログラミングについて触れられてるよ。[1] CUEは価値格子論理に基づいていて、LLMのNLPのいとこだけど、確定的で確率的ではないんだ。[2] LLMは文法的には正しいけど意味的には壊れた構成を生成することが多いから、CUEと一緒に使うことで設定やガードレールのリテラルプログラミングを改善できるんだ。[3] [1] CUEはすべてをこなすが、リテラルにできるか? (22件のコメント) https://news.ycombinator.com/item?id=46588607 [2] CUEの論理: https://cuelang.org/docs/concept/the-logic-of-cue/ [3] ガードレールの直感: 信頼できるAIに向けて: https://cue.dev/blog/guardrailing-intuition-towards-reliable...
一見するとワクワクするね。ナラティブ部分を書くだけで、LLMがコードを生成してくれて、そのままナラティブを残せば完成!労力なしでリテラルプログラミングができるってわけ。でも、二つの大きな問題があると思う。まず、ナラティブは線形に消費されるものだけど、コードはグラフとして消費される。記号から定義に移ったり、定義から使い方に飛んだりして、コードを理解するためにあちこち行き来する必要がある。リテラルプログラミングのナラティブ部分は、物語が主役でコードがその物語をサポートするノートブックにしか実際にはうまく機能しないんだ。次に、LLMを使ってコードを書くとき、説明する変更は通常、複数のファイルを同時に修正する必要がある。じゃあ、この「ナラティブ」はコードに対してどこに行くの?この二つの問題は密接に関連してるよね。
AIコードを読むためにコードレビューでリテラルプログラミングの一形態を使ってるよ。[Lean 4](lean-lang.org)とそのドキュメントツール[Verso](https://github.com/leanprover/verso/)を使って、リテラルエッセイを通じてコードを説明させてる。Leanと統合されてて、ちゃんと型チェックもされるから、すごく助かってる。