cl-macro-design

cxxxr's avatarfrom cxxxr

マクロ設計のベストプラクティスを適用。マクロ作成・レビュー時に使用

⭐1stars🔀0forks📁View on GitHub🕐Updated Dec 20, 2025

When & Why to Use This Skill

This Claude skill serves as an expert consultant for Common Lisp macro design, strictly adhering to the Google Common Lisp Style Guide. It helps developers implement robust, hygienic, and efficient macros by providing best practices for variable capture prevention, evaluation order control, and structural patterns like 'CALL-WITH'. It is designed to improve code quality and maintainability during both the creation and review phases of Lisp development.

Use Cases

  • Reviewing Common Lisp macros to identify and eliminate 'variable capture' risks using gensym and once-only utilities.
  • Designing complex Domain-Specific Languages (DSLs) while ensuring proper evaluation order and avoiding side-effect bugs.
  • Refactoring monolithic macros into the 'CALL-WITH' pattern to enhance debuggability and allow for runtime updates.
  • Verifying macro expansions (macroexpand-1) and ensuring adherence to professional naming conventions like the '-form' suffix for evaluated arguments.
  • Applying conditional compilation and resource management patterns (with-xxx) to ensure safe and efficient system-level programming.
namecl-macro-design
descriptionマクロ設計のベストプラクティスを適用。マクロ作成・レビュー時に使用
allowed-toolsRead, Grep, Glob, Bash

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)))