DEFMACRO — 매크로 정의하기

DEFMACRO — 매크로 정의하기 (매크로)

namemacro로 정의하는 가장 기본적인 매크로 정의 매크로예요. lambda-list의 destructuring을 통해 매크로 호출을 원하는 형태로 확장하는 함수를 만들어요. Common Lisp에서 새 문법(DSL)을 만드는 핵심 도구예요.

출처: DEFMACRO - Common Lisp HyperSpec

시그니처 (Syntax)

defmacro name lambda-list [[declaration* | documentation]] form* => name
  • namesymbol.
  • lambda-listmacro lambda list.
  • declarationdeclare expression. 평가되지 않아요.
  • documentationstring. 평가되지 않아요.
  • formform.

본문 (Description)

defmacro는 전역 환경에서 namemacro function을 연관지어 namemacro로 정의해요. macro functiondefmacro form이 나타나는 것과 같은 lexical environment에서 정의돼요.

lambda-list의 매개변수 변수들은 매크로 호출의 destructured된 부분들에 binding돼요.

확장 함수는 formenvironment 두 인자를 받아 form을 돌려줘요. 확장 함수의 본문은 forms로 지정돼요. forms는 순서대로 실행되고, 마지막으로 실행된 form의 값이 macro의 확장으로 돌려져요. 확장 함수의 본문 forms(lambda-list는 아님)는 name을 이름으로 하는 block 안에 암묵적으로 감싸져요.

lambda-list는 Section 3.4.4 (Macro Lambda Lists)의 요구사항을 따르도록 하세요.

documentationnamefunction 종류의 documentation string으로, 그리고 macro function에도 붙어요.

defmacromacro를 재정의하거나 function 정의를 macro 정의로 바꾸는 데 쓸 수 있어요.

돌려주는 form의 재귀 확장은 반드시 끝나야 해요. 여기에는 돌려준 다른 formsubform인 다른 macro의 확장도 포함돼요.

form을 완전히 매크로 확장한 결과에 literal object를 제외한 circular list structure가 포함되면 결과는 정의되지 않아요.

defmacro formtop level form으로 나타나면 compiler는 매크로 정의를 컴파일 시점에 저장해서, 파일 뒤쪽에서 그 매크로가 나타나는 곳을 올바르게 확장할 수 있어야 해요. 사용자는 compile되고 있는 파일 안에서 그 매크로를 참조한다면 macro 본문이 컴파일 시점에 평가될 수 있게 보장해야 해요.

예제 (Examples)

(defmacro mac1 (a b) "Mac1 multiplies and adds"
           `(+ ,a (* ,b 3))) =>  MAC1

(mac1 4 5) =>  19

(documentation 'mac1 'function) =>  "Mac1 multiplies and adds"

(defmacro mac2 (&optional (a 2 b) (c 3 d) &rest x) `'(,a ,b ,c ,d ,x)) =>  MAC2

(mac2 6) =>  (6 T 3 NIL NIL)
(mac2 6 3 8) =>  (6 T 3 T (8))

(defmacro mac3 (&whole r a &optional (b 3) &rest x &key c (d a))
   `'(,r ,a ,b ,c ,d ,x)) =>  MAC3

(mac3 1 6 :d 8 :c 9 :d 10) =>  ((MAC3 1 6 :D 8 :C 9 :D 10) 1 6 9 8 (:D 8 :C 9 :D 10))

내장 destructuring lambda list

내장 destructuring lambda listordinary lambda list 문법이 매개변수 이름은 허용하되 list는 허용하지 않는 자리에만 쓸 수 있다는 규정은 모호함을 막기 위한 것이에요. 예를 들어 다음은 유효하지 않아요:

(defmacro loser (x &optional (a b &rest c) &rest z)
  ...)

그 이유는 ordinary lambda list 문법이 &optional 뒤에 list를 허용하기 때문이에요. (a b &rest c)라는 list는 이름이 a이고 기본값이 b라는 form의 값이며, supplied-p 매개변수 이름이 &rest(유효하지 않음), 그리고 목록에 의미 없는 심볼 c(역시 유효하지 않음)가 있는 optional 매개변수로 해석될 거예요. 거의 맞는 표현 방법은 이래요:

(defmacro loser (x &optional ((a b &rest c)) &rest z)
  ...)

괄호를 한 겹 더 써서 모호함을 제거해요. 하지만 이 정의는 이제 틀렸어요. (loser (car pool)) 같은 매크로 호출은 lambda list (a b &rest c)에 줄 인자 form을 제공하지 않으므로, 명시적 기본값이 없어서 lambda list와 매칭할 기본값은 nil이 되거든요. 빈 list인 nil에는 매개변수 a와 b를 채울 form이 없으므로 결과는 불특정(unspecified)이에요. 완전히 올바른 정의는 다음 중 하나예요:

(defmacro loser (x &optional ((a b &rest c) '(nil nil)) &rest z)
  ...)

또는

(defmacro loser (x &optional ((&optional a b &rest c)) &rest z)
  ...)

둘은 약간 달라요. 첫 번째는 매크로 호출이 a를 명시하면 b도 반드시 명시해야 하지만, 두 번째는 그 요구가 없어요. 예를 들어

(loser (car pool) ((+ x 1)))

는 두 번째 정의에서는 유효한 호출이지만 첫 번째에서는 아니에요.

&whole 관련 예제

(defmacro dm1a (&whole x) `',x)
(macroexpand '(dm1a))  =>  (QUOTE (DM1A))
(macroexpand '(dm1a a)) is an error.

(defmacro dm1b (&whole x a &optional b) `'(,x ,a ,b))
(macroexpand '(dm1b))  is an error.
(macroexpand '(dm1b q))  =>  (QUOTE ((DM1B Q) Q NIL))
(macroexpand '(dm1b q r)) =>  (QUOTE ((DM1B Q R) Q R))
(macroexpand '(dm1b q r s)) is an error.
(defmacro dm2a (&whole form a b) `'(form ,form a ,a b ,b))
(macroexpand '(dm2a x y)) =>  (QUOTE (FORM (DM2A X Y) A X B Y))
(dm2a x y) =>  (FORM (DM2A X Y) A X B Y)

(defmacro dm2b (&whole form a (&whole b (c . d) &optional (e 5))
                &body f &environment env)
  ``(,',form ,,a ,',b ,',(macroexpand c env) ,',d ,',e ,',f))
;Note that because backquote is involved, implementations may differ
;slightly in the nature (though not the functionality) of the expansion.

(macroexpand '(dm2b x1 (((incf x2) x3 x4)) x5 x6))
=>  (LIST* '(DM2B X1 (((INCF X2) X3 X4))
                  X5 X6)
           X1
           '((((INCF X2) X3 X4)) (SETQ X2 (+ X2 1)) (X3 X4) 5 (X5 X6)),
    T

(let ((x1 5))
  (macrolet ((segundo (x) `(cadr ,x)))
    (dm2b x1 (((segundo x2) x3 x4)) x5 x6)))
=>  ((DM2B X1 (((SEGUNDO X2) X3 X4)) X5 X6)
    5 (((SEGUNDO X2) X3 X4)) (CADR X2) (X3 X4) 5 (X5 X6))

더 알아보기 (See Also)

define-compiler-macro, destructuring-bind, documentation, macroexpand, macroexpand-hook, macrolet, macro-function, Section 3.1 (Evaluation), Section 3.2 (Compilation), Section 3.4.11 (Syntactic Interaction of Documentation Strings and Declarations)

Notes

없음.