なぜLISPで独自言語が作れるのか †
- LISPの特徴:コードもデータも同じリスト形式
- マクロによるコード変換の仕組み
- 独自言語(DSL)作成におけるマクロの役割
具体例:簡単な独自言語を作ってみる †
目標:レシピ管理言語 †
;; こんな感じで書きたい(独自言語)
(recipe カレー
(材料
(玉ねぎ 2個)
(にんじん 1本)
(じゃがいも 2個))
(手順
(切る 玉ねぎ さいの目)
(炒める 玉ねぎ "キツネ色になるまで")
(加える 水 500ml)))
;; 展開後の一般的なLISPコード
(defun カレー ()
(let ((ingredients
'((onion 2 :unit "個")
(carrot 1 :unit "本")
(potato 2 :unit "個"))))
(prepare-recipe
(cut :target 'onion :style 'dice)
(saute :target 'onion :until "キツネ色になるまで")
(add :item 'water :amount 500 :unit "ml"))))
マクロの実装 †
(defmacro recipe (name &rest body)
`(defun ,name ()
...))
(defmacro 材料 (&rest items)
...)
(defmacro 手順 (&rest steps)
...)
独自言語設計のポイント †
- 対象ドメインの概念を直接表現できる
- 一般的なLISPコードへの変換規則を決める
- エラーチェックやバリデーションの追加
発展例:実用的な独自言語 †
- テスト記述言語
- 設定ファイル記述言語
- ワークフロー記述言語
Lispマクロの基本構文 †
マクロ定義の基本形 †
defmacro マクロ名 (引数リスト) 本体
引数リストで使える特殊記号 †
- &rest:残りの全ての引数をリストとして受け取る
- 例:(defmacro example (x &rest body)) で、bodyに残りの全引数が入る
バッククォート記法 †
- `(バッククォート):テンプレートとしてリストを作る
- ,(カンマ):バッククォート内で変数を評価する
- ,@(カンマアットマーク):リストを展開して埋め込む
バッククォートの例 †
(let ((x 5) (lst '(2 3)))
`(1 ,x ,@lst))
;; => (1 5 2 3)
実践的なマクロ例 †
(defmacro my-when (condition &rest body)
`(if ,condition
(progn ,@body)))
展開例 †
;; マクロ使用
(my-when (> x 0)
(print "正の数")
(print x))
;; 展開結果
(if (> x 0)
(progn
(print "正の数")
(print x)))
マクロ展開の注意点 †
- マクロは「コードを生成するコード」
- コンパイル時に展開される
- バッククォートとカンマを使って適切にコードを生成する必要がある
マクロ使用時のデバッグ †
- macroexpand-1:1段階だけマクロを展開
- macroexpand:完全にマクロを展開