Blog

Macro, Special Form

November 18, 2013

Macro, Special Form

Special Form and MACRO

Special Form에 대해 관심을 가지게 된 이유는, 여기저기 등장하기 때문. 가끔 CLHS를 보면 어떤 심볼이 Function이 아니라 Special Form일때가 많더라.

여기 에 따르면, 리스트를 평가할때는 첫 심볼을 함수로, 나머지 원소를 함수의 인자들로 표현하는 리스트 형태(Form)가 기본이다. 그러나 때때로 예외가 존재하는데, 이들을 Special Form 이라고 한단다.

http://www.n-a-n-o.com/lisp/cmucl-tutorials/LISP-tutorial-10.html 에 있는 Lisp Tutorial에 따르면, Speical Form은 다양하다.

– If statements
– do loop
– setq, setf, push, pop
– defun, let
– function

이런 Special Form들은 함수같아 보이지만, 실제로 아니다. 예를들어 If의 경우에는, 뒤에 인자로 오는 것들을 '평가' 하지 않는다. function의 경우에도 마찬가지다.

function Causes its argument to be interpreted as a function rather than being evaluated

https://groups.google.com/forum/#!msg/lisp-korea/dGZjZgOZg8o/Fivhmg3RH74J 한국 리스퍼에 있는 메일링 리스트에 따르면, Lisp 해석기는 일반적인 Lisp Form을 해석할때 함수 이름을 확인하는데, if, let 등등 몇 가지 심볼에 대해서는 특별한 처리를 해준다. 그리고 이들을 Special Form 이라고 한다.

결국 Special Form 은 매크로인듯 하다. 그리고, 이런 Special Form을 만드는 이유는, 일반 함수와는 다른 평가 방법을 적용하길 원하기 때문이다. 여기에 있는 예제를 보자.

(if foo bar zoo)

의 경우, 만약 foo 가 참이라면 bar를 평가하고, 아니라면 zoo를 평가하면 된다. 그러나 이건 Lisp의 기본적인 Form 평가 방법이 아니다. Lisp은 foo, bar, zoo 를 모두 평가하여 if 라는 함수에 넘겨줄 것이다. 따라서 다른 평가 방법을 사용하는 Special Formif 가 필요하다.

  (defun my-if (cond then-clause else-clause)
    (if cond (funcall then-clause)
        (funcall else-clause)))

이런 코드를 작성한다면, then-caluseelse-caluse 에 람다(함수객체)를 넘겨주어 Funcall 을 통해 평가를 실행시점에 해 낼 수 있다. 아래처럼

 (my-if (> 1 2) (lambda () (print "big!"))
           (lambda () (print "small!")))

여기 에서 말하듯이, Python 이라면 위와 같은 방법을 사용했을 것이고, 자바라면 호출가능한 메서드 규악을 인터페이스로 만들어서 그 구현을 전달 했을 거란다. 하지만, 우리의 강력한 Lisp은 평가 시점을 조절하기 위해 Macro (드디어 나왔다!) 를 제공한다.

  (defmacro my-if-2 (cond then-clause else-clause)
    `(if ,cond
       ,then-clause
       ,else-clause))

그리고 이런 매크로 인자들은 Macro-expention단계에서는 평가되지 않는다. 실행하는 시점에서야 평가가 이루어지므로, 람다를 넘겨주는 등의 작업 없이 아래와 같이 사용가능하다.

 (my-if-2 (> 1 2) (print "big") (print "small"))

그리고 실제 이렇게 확장 할 수 있단다.

  CL-USER> (macroexpand '(my-if-2 (> 1 2) (print "big") (print "small")))
  (IF (> 1 2) (PRINT "big") (PRINT "small"))
  T

결국 매크로란, Lisp Form 이라는 Lisp Object를 받아서 매크로 함수에 전달한 뒤, 새로운 Lisp Form을 되돌리는, 단순히 리스트를 조작하는 함수라는 것. 실제로 defmacro를 통해 매크로를 정의하게 되면, 심볼 테이블에 매크로가 존재하게 되고 macro-function 을 통해 접근 가능하다.

http://stackoverflow.com/questions/17137542/common-lisp-why-progn-is-a-special-form 에는 비슷한 질문이 있다. 이 질문을 통해서 Macro를 간접적으로 이해할 수 있는데, 질문의 요지는 이거다.

Why progn is a special form?

예를들어, progn 을 구현하기 위해 defun 으로 아래와 같은 함수를 구현하는것은 왜 안되느냐는 질문

(defun progn2 (&rest body)
  (first (last body)))

With using PROGN the compiler will treat the DEFMACRO form as a top-level form. That means for example that the compiler notes that there is a macro definition and makes it available in the compile-time environment.

Using a function MY-PROGN, the compiler won’t recognize the DEFMACRO form, because it is not at top-level.

Top-level form 이란 평가되지 않는 form 을 말한다. 이런 form은 function 으로는 인자로 받을 수 없고, 매크로를 이용해야 하므로 prognSpeical Form으로 구현되었다는 것. 그리고 이런 차이로 인해서 다음과 같은 결과도 발생한다.

(progn (values 1 2 3)) 
=>  1, 2, 3
(progn2 (values 1 2 3)) 
=>  1

이에 대한 외국 성님의 해설은.. 명쾌하다

Another critical feature of progn (mentioned by Rainer first) is that it keeps all its forms top-level, which makes it possible for macros to expand to multiple forms (see, e.g., my answer to ”“value returned is unused” warning when byte-compiling a macro”).

Macro kepps all its forms top-level, which makes it possible for macro to expand to multiple forms ㅠㅠ 매크로는 평가를 지연시켜, 복수번의 매크로 확장이 일어날 수 있도록 함.

다음 이야기는, Dynamic Scope vs Lexical Scope 가 될 것 같다. Let, Flet 그런것들에 대한 이야기가 될 듯.

Array