install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/cl-macro-design" ~/.claude/skills/majiayu000-claude-skill-registry-cl-macro-design && rm -rf "$T"
manifest:
skills/data/cl-macro-design/SKILL.mdsource content
Common Lisp Macro Design
Based on Google Common Lisp Style Guide (@references/google-common-lisp-style-guide.md)
基本原則
1. マクロを使うべき場面
- 新しい制御構造の定義
- コンパイル時の計算
- ボイラープレートの削減
- DSLの構築
2. 関数で十分な場合
- 単純なデータ変換
- 実行時の値に依存する処理
- 高階関数で表現可能な場合
3. パフォーマンス目的ならINLINE宣言を使う
;; BAD - パフォーマンスのためにマクロ化 (defmacro fast-add (a b) `(+ ,a ,b)) ;; GOOD - inline宣言で同等の効果 (declaim (inline fast-add)) (defun fast-add (a b) (+ a b))
衛生的マクロ (Hygienic Macros)
変数捕捉の防止
;; BAD - 変数捕捉の危険 (defmacro with-timing-bad (&body body) `(let ((start (get-internal-real-time))) ,@body (- (get-internal-real-time) start))) ;; GOOD - gensymで安全 (defmacro with-timing (&body body) (let ((start (gensym "START"))) `(let ((,start (get-internal-real-time))) ,@body (- (get-internal-real-time) ,start))))
with-gensyms ユーティリティ
;; Alexandriaのwith-gensymsを使用 (defmacro with-retry ((var max-attempts) &body body) (with-gensyms (attempt result success) `(loop for ,attempt from 1 to ,max-attempts do (multiple-value-bind (,result ,success) (ignore-errors ,@body) (when ,success (return ,result))) finally (error "Max attempts exceeded"))))
複数評価の防止
問題のあるコード
;; BAD - 引数が2回評価される (defmacro square-bad (x) `(* ,x ,x)) (square-bad (incf n)) ; nが2回インクリメントされる!
正しい実装
;; GOOD - 一度だけ評価 (defmacro square (x) (let ((v (gensym))) `(let ((,v ,x)) (* ,v ,v)))) ;; once-only ユーティリティを使用 (Alexandria) (defmacro square (x) (once-only (x) `(* ,x ,x)))
マクロ展開の検証
展開確認コマンド
;; 1段階展開 (macroexpand-1 '(with-timing (heavy-computation))) ;; 完全展開 (macroexpand '(with-timing (heavy-computation))) ;; プリティプリント (pprint (macroexpand-1 '(your-macro args)))
SLIMEでの確認
;; Emacs/SLIME: C-c C-m (slime-expand-1) ;; カーソル位置のマクロを展開表示
構造パターン
パターン0: CALL-WITH スタイル (Google推奨)
マクロは構文処理のみを担当し、セマンティクスは補助関数に委譲する。
;; マクロは薄いラッパー (defmacro with-foo (() &body body) `(call-with-foo (lambda () ,@body))) ;; セマンティクスは関数で実装 (defun call-with-foo (thunk) (setup-foo) (unwind-protect (funcall thunk) (teardown-foo)))
利点:
- デバッグ時にスタックトレースに関数名が現れる
- 実行時に関数を更新可能(再コンパイル不要)
- マクロの複雑さを軽減
パターン1: with-xxx (リソース管理)
(defmacro with-open-database ((var connection-string) &body body) (let ((db (gensym "DB"))) `(let ((,db (connect-database ,connection-string))) (unwind-protect (let ((,var ,db)) ,@body) (disconnect-database ,db)))))
パターン2: do-xxx (イテレーション)
(defmacro do-lines ((var stream &optional result) &body body) (let ((s (gensym "STREAM"))) `(let ((,s ,stream)) (loop for ,var = (read-line ,s nil nil) while ,var do (progn ,@body) finally (return ,result)))))
パターン3: define-xxx (定義マクロ)
(defmacro define-api-endpoint (name (method path) &body body) `(progn (defun ,name (request) ,@body) (register-endpoint ,method ,path #',name) ',name)) ;; 使用 (define-api-endpoint get-users (:get "/api/users") (fetch-all-users))
パターン4: 条件付きコンパイル
(defmacro debug-log (format-string &rest args) #+debug `(format *debug-io* ,(concatenate 'string "[DEBUG] " format-string "~%") ,@args) #-debug nil)
チェックリスト
作成前
- 本当にマクロが必要か?関数で十分ではないか?
- 類似の標準マクロはないか?
実装時
- すべての内部変数にgensymを使用
- 引数は一度だけ評価される
- 評価順序は直感的
- &bodyは適切な位置に配置
検証時
- macroexpand-1で展開を確認
- 副作用のある引数でテスト
- ネストした使用でテスト
- エッジケースでテスト
ドキュメント
- docstringで目的を説明
- 使用例を含める
- 展開例を示す
パラメータ設計 (Google Style Guide)
パラメータ命名規約
評価される形式には
-form サフィックスを付ける:
;; 明確: condition-form は評価される式 (defmacro when-available (condition-form &body body) `(when ,condition-form ,@body)) ;; 例外: body, end は慣例的なので -form 不要
拡張可能なパラメータスペース
将来の拡張に備えて空のパラメータリストを置く:
;; BAD - 拡張が困難 (defmacro with-lights-on (&body body) ...) ;; GOOD - 後からオプションを追加可能 (defmacro with-lights-on (() &body body) ...) ;; 拡張例 (defmacro with-lights-on ((&key color intensity) &body body) ...)
alexandria:once-only の活用
引数の複数評価を防止:
(defmacro double (value-form) (alexandria:once-only (value-form) `(+ ,value-form ,value-form)))
アンチパターン
1. 過度なマクロ使用
;; BAD - 関数で十分 (defmacro add-one (x) `(+ ,x 1)) ;; GOOD (defun add-one (x) (+ x 1))
2. 巨大なマクロ
;; BAD - マクロ内に大量のロジック (defmacro complex-operation (...) `(progn ;; 100行のコード... )) ;; GOOD - ヘルパー関数に分離 (defun %complex-operation-impl (...) ;; 実装 ) (defmacro complex-operation (...) `(%complex-operation-impl ...))
3. 不明瞭な副作用
;; BAD - 副作用が分かりにくい (defmacro with-user (user &body body) `(let ((*current-user* ,user)) (log-user-action ,user) ; 隠れた副作用 ,@body)) ;; GOOD - 明示的 (defmacro with-user (user &body body) "Bind *CURRENT-USER* and log the action." `(let ((*current-user* ,user)) ,@body)) ;; ログは呼び出し側で明示 (with-user user (log-user-action user) (do-something))
禁止事項 (Google Style Guide)
1. 新しいリーダーマクロの無許可導入禁止
;; 禁止: プロジェクト外に露出するリーダーマクロ (set-macro-character #\[ ...) ; 危険 ;; 許可される場合のみ: named-readtables で制御 (named-readtables:in-readtable :my-syntax)
2. 実行時のEVAL禁止
;; 禁止: セキュリティ脆弱性 (eval (read-from-string user-input)) ;; 許可: 開発ツール、ビルド基盤のみ
3. バッククォート内での複雑なロジック禁止
;; BAD - バッククォート内でロジック (defmacro complex (&body body) `(progn ,@(if (some-condition body) (transform-body body) body))) ;; GOOD - ロジックを外に出す (defmacro complex (&body body) (let ((processed (if (some-condition body) (transform-body body) body))) `(progn ,@processed)))