Org Babelを用いたリテレートプログラミングとタングリング
Org BabelはDonald Knuthのリテレートプログラミング(文芸的プログラミング)パラダイムの実装です。コードにコメントを散りばめる代わりに、人間向けの散文、アーキテクチャノート、設計根拠を記述し、その中に関数コードブロックを埋め込みます。
単一のOrgファイルが、以下の2つの異なる出力の信頼できる唯一の情報源(Single Source of Truth)として機能します。
- ウィービング(人間向けドキュメント): ノートブックを整形されたHTML、LaTeX/PDF、またはMarkdownにエクスポートして読者に提供します。
- タングリング(機械実行): コンパイラ、インタプリタ、デプロイシステム向けに、純粋で実行可能なソースコードファイル(
.py、.sh、.c、.conf、.el)を抽出します。
1. タングリングの基本: :tangle ヘッダー引数
デフォルトでは、コードブロックはタングルされません。:tangle 系のヘッダー引数で、ブロックをどのようにどこにディスクへ抽出するかを指定します。
1.1 :tangle 引数の値
| 値 | 動作 |
|---|---|
:tangle no (デフォルト) | ブロックはいかなるファイルにも書き出されません。(<<noweb>>を使って他のブロックに含めることは可能です) |
:tangle "path/to/file.py" | ブロック本体を指定されたファイルパスに直接書き出します(バッファディレクトリからの相対パス、または絶対パス)。 |
:tangle yes | Orgファイルのベース名と言語の標準ファイル拡張子でファイルにタングルします(例: model.org -> model.py)。 |
1.2 その他のタングリングヘッダー引数
:shebang "#!/usr/bin/env python3": タングルされたファイルの先頭行に指定されたシバン文字列を追加します。:tangle-mode (identity #o755): 生成されたファイルのファイルシステムパーミッションを設定します(例:#o755でスクリプトを実行可能にします)。:no-expand: 変数代入(:var)がタングルコードにインライン展開されるのを防ぎ、生のブロック本体のみを書き出します。:mkdirp yes: タングルパスに存在しない親ディレクトリを自動的に作成します。
2. タングリングのスコープ: ブロック、サブツリー、バッファレベル
タングリング引数はプロジェクト全体にわたって整理してスコープ指定できます。
2.1 サブツリーレベルのタングリング(推奨パターン)
異なる見出しで、異なるファイルやモジュールにタングルできます。
* Core Utilities
:PROPERTIES:
:header-args:python: :tangle src/utils.py :mkdirp yes
:END:
#+BEGIN_SRC python
def slugify(text: str) -> str:
return "-".join(text.lower().split())
#+END_SRC
* Data Processing Pipeline
:PROPERTIES:
:header-args:python: :tangle src/pipeline.py :mkdirp yes
:END:
#+BEGIN_SRC python
from utils import slugify
def process_record(rec: dict) -> dict:
rec["slug"] = slugify(rec["name"])
return rec
#+END_SRC
2.2 バッファ全体のタングリングデフォルト
ファイルの先頭で設定します:
#+PROPERTY: header-args:python :tangle src/main.py
3. Noweb構文: モジュール式コードの組み立て
Noweb を使用すると、より大きなソースファイルを、小さくモジュール化された、簡単に説明できる断片から組み立てられます。<<block_name>> を使用して、あるコードブロックを別のコードブロック内で参照します。
3.1 Noweb展開の有効化
Nowebはデフォルトで無効です。:noweb ヘッダー引数で制御します:
:noweb の値 | Emacsで評価されるか? | ディスクにタングルされるか? | ドキュメントにエクスポートされるか? |
|---|---|---|---|
no (デフォルト) | いいえ | いいえ | いいえ |
yes | はい | はい | はい |
tangle | いいえ | はい | いいえ |
strip-tangle | はい | いいえ (参照は削除されます) | はい |
no-export | はい | はい | いいえ |
eval | はい | いいえ | いいえ |
3.2 Nowebの例: リテレートスクリプトの構築
#+NAME: imports
#+BEGIN_SRC python :exports none
import sys
import argparse
from pathlib import Path
#+END_SRC
#+NAME: argument-parser
#+BEGIN_SRC python :exports code
parser = argparse.ArgumentParser(description="Process dataset.")
parser.add_argument("--input", required=True, type=Path, help="Input CSV path")
parser.add_argument("--threshold", default=0.5, type=float, help="Score cutoff")
args = parser.parse_args()
#+END_SRC
#+NAME: process-data
#+BEGIN_SRC python :exports code
print(f"Loading data from {args.input}...")
# Processing logic here
#+END_SRC
# 最終的な実行可能スクリプトの組み立て:
#+NAME: main-cli
#+BEGIN_SRC python :tangle bin/run_pipeline.py :shebang "#!/usr/bin/env python3" :tangle-mode (identity #o755) :noweb yes :exports none
<<imports>>
def main():
<<argument-parser>>
<<process-data>>
if __name__ == "__main__":
main()
#+END_SRC
タングルされると、bin/run_pipeline.py は、すべての断片が適切に配置されインデントされた、クリーンで実行可能なスクリプトとして書き出されます!
3.3 Nowebのプレフィックスとインデント
- Nowebはインデントを尊重します。
def main():内で<<argument-parser>>が4スペースインデントされている場合、展開されたブロックのすべての行が4スペースでインデントされます。 - プレフィックス動作を無効にするには:
:noweb-prefix no - ソースコードの代わりにブロックの実行結果を挿入するには、括弧を追加します:
<<compute_constant()>>
3.4 展開のデバッグ
タングリングや実行の前に、noweb ブロックがどのように展開されるかを確認するには:
- ブロック内にポイントを置き、
C-c C-v v(org-babel-expand-src-block) を押します。 - Org は、すべての noweb および
:var置換後の正確なコードを示す一時バッファを開きます。
4. 設定とデータのタングルファイルへのインライン化
Babel は、Org テーブルや見出しプロパティの変数をタングルコードに直接インライン化できます:
# Org テーブルに格納された設定:
#+NAME: server_config
| Key | Value |
|---------+------------------|
| host | 10.0.1.25 |
| port | 8080 |
| timeout | 30 |
# タングルされる設定モジュール:
#+BEGIN_SRC python :tangle config.py :var cfg=server_config :colnames yes
HOST = cfg[0][1]
PORT = int(cfg[1][1])
TIMEOUT = int(cfg[2][1])
#+END_SRC
Babel がタングルコード内に変数ラッパーを生成するのを防ぐには、:no-expand を渡します。
5. インデント処理: ホワイトスペース依存言語
Python や YAML のようなホワイトスペースに依存する言語では、Org の自動再インデントが構文を壊す可能性があります。
Python タングリングにおける重要なルール:
org-src-preserve-indentation を t に設定します。これが t の場合、Org はエクスポートおよびタングリング時にインデントを追加したり削除したりしません:
;; Emacs 設定ファイル内:
(setq org-src-preserve-indentation t)
または、ブロックごとにスイッチ -i を使用:
#+BEGIN_SRC python -i :tangle script.py
6. タングリングの実行方法
6.1 Emacs 内での対話的操作
| キーバインド | コマンド | 説明 |
|---|---|---|
C-c C-v t / C-c C-v C-t | org-babel-tangle | 現在のバッファ内のすべてのコードブロックをタングルします。 |
C-c C-v f / C-c C-v C-f | org-babel-tangle-file | ディスク上の Org ファイルを選択してタングルします。 |
6.2 タングルファイルから Org へのナビゲーション
タングルされると、Babel は生成ファイルにバックリンクコメントを挿入します:
# [[file:~/project/notebook.org::*Core Utilities][core-slugify]]
def slugify(text: str) -> str:
...
# core-slugify ends here
Emacs で、生成されたファイルを開き、M-x org-babel-tangle-jump-to-org を実行すると、ソースの .org ドキュメント内の正確な見出しとブロックに瞬時にジャンプします!
6.3 ヘッドレスコマンドライン / CI / Make バッチタングリング
Emacs GUI ウィンドウを開かずにファイルをタングルします(ビルドスクリプト、Makefile、継続的インテグレーションに最適):
# 単一ファイルのタングル:
emacs -Q --batch \
--eval "(require 'org)" \
--eval "(require 'ob-tangle)" \
--eval '(org-babel-tangle-file "analysis.org")'
# シェルループまたは Makefile から複数ファイルのタングル:
emacs -Q --batch --eval "
(progn
(require 'org)
(require 'ob-tangle)
(dolist (file command-line-args-left)
(with-current-buffer (find-file-noselect file)
(org-babel-tangle))))" *.org
7. リテレート設定管理: init.org パターン
Org Babel リテレートプログラミングの人気のあるユースケースは、Emacs 設定やシステムドットファイルの管理です。
~/.emacs.d/init.el 内:
;; Bootstrap Org Babel literate configuration
(require 'org)
(require 'ob-tangle)
(org-babel-load-file (expand-file-name "config.org" user-emacs-directory))
org-babel-load-file は自動的に、config.org が最後に config.el が生成されてから変更されたかどうかを確認し、必要に応じて新しいバージョンをタングルし、結果の .el ファイルをロードします。
8. タングリングプロジェクトのためのチェックリストまとめ
- 明示的なタングリングパス: 明示的なファイル名を使用します(例:
:tangle "src/core.py")。 - 可読性のために Noweb を使用: 複雑なアルゴリズムを論理的なステップ(
<<init-weights>>、<<forward-pass>>、<<backprop>>)に分割して、読者のために説明が自然に流れるようにします。 - インデントを保持: Python コードの場合、常に
org-src-preserve-indentationがtであることを確認します。 - Make/CI で自動化: ビルドシステムにヘッドレス
emacs --batchタングリングステップを含め、生成されたソースコードを Org ドキュメントと同期させます。