2. Introduction
Macro
호칭
Macro function or 줄여서 macro
용도
Lisp의 syntax를 확장하는 하나의 방법
Tool
PPMX(pretty print macro expansion)
Compiler
역할
Lisp프로그램을 기계언어로 바꾸는 역할
효과
일반적으로 10 ~ 100배 정도의 성능 향상
3. Macro as shorthand
(INCF A) vs. (SETQ A (+ A 1))
매크로는 복잡한 표현을 간략하게 할 수 있음
SETF vs. SETQ
매크로 SETF, INCF는 special 함수 SETQ보다 smart함
(incf (aref (nth array-num *list-of-arrays*)
(first subscripts)))
DEFSTRUCT 매크로
MAKE-STARSHIP, STARSHIP-P, STARSHIP-NAME 의 함
수를 암묵적으로 생성
4. Macro expansion
Lisp의 Macro는 자동으로 표현식을 확장
입력 인자를 평가하지 않는 약어 확장 함수
> (ppmx (incf a))
Macro expansion:
(SETQ A (+ A 1))
> (ppmx (incf a))
Macro expansion:
(LET ((#:G0144 (+ A 1)))
(SETQ A #:G0144))
5. Defining a macro
DEFMACRO
매크로는 defmacro를 이용해서 정의
Syntax는 defun과 유사
Macro 함수는 평가되어 질 수 있는 표현식을 만들어 반환
간단한 버전의 incf
list를 이용해서 lisp이 평가할수있는 표현식 생성
(defmacro simple-incf (var)
(list ’setq var (list ’+ var 1)))
(setf a 4)
> (simple-incf a)
5
(defmacro simple-incf (var &optional (amount 1))
(list ’setq var (list ’+ var amount)))
(setf b 2)
> (simple-incf b (* 3 a))
17
6. Why macro?
Function or MACRO
INCF 함수는 매크로로만 만들 수 있다.
함수버전 incf
(defun faulty-incf (var)
(setq var (+ var 1)))
(setf a 7)
> (faulty-incf a)
8
> (faulty-incf a)
8
> a
7 ;; a 값이 증가 되지 않음
인자 값이 함수로 들어가기 전에 평가되어짐
증가한 값은 함수내의 지역 변수 var이지 a가 아님
7. Macros as syntactic extensions
Macro의 목적
Lisp Language의 syntax확장
Macro vs. Function
1. Function의 인자는 항상 평가 된다. Macro의 인자는 평가
되지 않는다.
2. Function의 결과값은 어떤 것이든지 될 수 있다. Macro의
반환 값은 반드시 유효한 Lisp 표현이어야 한다.
3. Macro가 반환한 표현은 즉시 평가되어 진다. Function의
반환 결과는 평가되어 지지 않는다.
8. Macros as syntactic extensions
Special functions
SETQ, IF, LET, BLOCK 등..
Common lisp의 가장 하위 레벨의 블록들
Scoping, block 과 loop과 같은 기본적인 제어구조를 책임
짐
Macro와 마찬가지로 인자들을 평가하지 않음
평가하기 위한 표현을 반환하자 않음
새로운 special function을 쓸 수 없음, lisp implementer만
이 할 수 있음
9. The backquote character
Backquote [ ` ]
Quote와 유사하게 list를 인용하기 위해서 사용
Unquote
Comma [ , ]
Backquote된 list의 어떤 값 앞에 comma가 붙게 되면 unquote됨
Unquote는 쓰여진 표현이 자체가 아니가 그것의 값을 의미
(setf name ’fred)
> `(this is ,name from pittsburgh)
(THIS IS FRED FROM PITTSBURGH)
> `(i gave ,name about ,(* 25 8) dollars)
(I GAVE FRED ABOUT 200 DOLLARS)
Simple-incf
(defmacro simple-incf (var) ;; list를 사용한 매크로
(list ’setq var (list ’+ var 1)))
(defmacro simple-incf (var &optional (amount 1)) ;; backquote를 사용한 매크로
`(setq ,var (+ ,var ,amount)))
10. Splicing with backquote
Splice
Comma-at [ ,@ ]
,@에 의해서 생기는 결과 값은 단순히 삽입되는 것이 아니라 접
합되어(spliced)짐
,@는 가장 외각의 괄호가 없어 지는 역할을 하기 때문에 반드시
list에서만 적용해야 함
(setf name ’fred)
(setf address ’(16 maple drive))
> `(,name lives at ,address now) ;;
Inserting.
(FRED LIVES AT (16 MAPLE DRIVE) NOW)
> `(,name lives at ,@address now) ;;
Splicing.
(FRED LIVES AT 16 MAPLE DRIVE NOW)
11. The compiler
Function compilation
COMPILE 이라는 명령어를 통해서 가능
File compilation
COMPILE-FILE 이라는 명령어를 통해서 할 수 있음
성능
10 ~ 100배정도 향상 가능
12. Compiling entire programs
에러메시지 처리
Global variable
Global variable을 사용하면 “assumed to be SPECIAL” 이라는 경고
문구가 출력됨
DEFVAR, DEFPARAMETER, DEFCONSTANT를 적절하게 이용하
여 경고 메시지를 없앨 수 있음
선언은 파일의 초반부 OR 그 값을 참조하는 함수 전에 선언되어야
함
MACRO
매크로의 선언은 반드시 어떤 함수가 그것을 참조하기 전에 놓여져
야 함
함수 foo 가 매크로 bar를 부를 때 bar가 foo보다 뒤에 있다면 foo를
컴파일 할 때 매크로 bar를 확장해야 하는 것을 알지 못함
Built-in 함수
내장함수 재정의 하면 컴파일 에러가 남
13. Advanced topics:
The &body lambda-list keyword
WHILE loop in lisp
(defmacro while (test &body body)
`(do ()
((not ,test))
,@body))
매크로를 통해서 새로운 syntax를 추가함
&body
&body는 &rest와 동일한 기능
남겨진 인자들이 list의 형태로 넘어옴
매크로를 읽을 때 나머지 부분들이 Lisp code의 body부분
이 된다는 것을 알려줌
14. Advanced topics:
Destructuring lambda lists (1)
Destructuring
매크로는 입력인자를 평가 안 함
입력 표현을 자동으로 분리될 수 있도록 List 처럼 취급 가능
매크로에서 가능
복잡한 syntax 제어 구조에 용이
(defmacro mix-and-match (p q) ;; not destruturing
(let ((x1 (first p))
(y1 (second p))
(x2 (first q))
(y2 (second q)))
`(list ’(,x1 ,y1)
’(,x1 ,y2)
’(,x2 ,y1)
’(,x2 ,y2))))
(defmacro mix-and-match ((x1 y1) (x2 y2)) ;; destruturing
`(list ’(,x1 ,y1)
’(,x1 ,y2)
’(,x2 ,y1)
’(,x2 ,y2)))
15. Advanced topics:
Destructuring lambda lists - example
DOVECTOR
(defmacro dovector ((var vector-exp
&optional result-form)
&body body)
`(do* ((vec-dov ,vector-exp)
(len-dov (length vec-dov))
(i-dov 0 (+ i-dov 1))
(,var nil))
((equal i-dov len-dov) ,result-form)
(setf ,var (aref vec-dov i-dov))
,@body))
> (dovector (x ’#(foo bar baz))
(format t "~&X is ~S" x))
X is FOO
X is BAR
X is BAZ
NIL
vec-dov, len-dov, i-dov 는 초기값을 담기 위한 지역 변수
변수 이름 충돌을 막기 위해서 package system과 gensyms를 사용하는 것이 바람직하지만
이 책의 범위를 벗어 나는 내용
17. Advanced topics:
Macros and lexical scoping
함수로 incf 만들기
값이 평가되는 것이 피하기 위해 symbol을 사용함
변수에 대한 Read & Update가 가능해야 함
전역변수는 가능
(defun faulty-incf (var)
(set var (+ (symbol-value var) 1)))
(setf a 7)
> (faulty-incf ’a)
8
> (faulty-incf ’a)
9
> a
9
지역 변수는 매크로만 가능
(defun test-simple (turnip)
(simple-incf turnip))
(defun test-faulty (turnip)
(faulty-incf ’turnip))
> (test-simple 37)
38
> (test-faulty 37)
Error: TURNIP unassigned variable.
FAULTY-INCF의 부모 lexical-context는 global-context이기 때문에 TEST-FAULTY의 지역변수 TURNIP에 lexically하
게 접근이 되어지지 않는다.
18. Advanced topics:
Dynamic scoping
Lexical scoping
정의
X변수에 접근하기 위해서는 X가 정해진 body 안에서만 접근가능
전역함수
DEFUN으로 정의 되어짐
자신의 local 변수와 전역 변수 접근 가능
지역함수
함수 BAR안에 lambda 표현 식으로 정의
자신의 변수, BAR의 변수와 전역 변수 접근 가능
Dynamic scoping
Dynamic variable은 Special variable이라고도 부름
정의
변수 X가 special로 선언되면 다른 함수의 지역 변수가 될 수 없고
어디서나 접근이 가능
DEFVAR 매크로를 통해 정의될 수 있음
19. Advanced topics:
Dynamic scoping - example
(defvar birds) ;; bird를 special로 선언
(setf fish ’(salmon tuna)) ;; fish – lexical
(setf birds ’(eagle vulture)) ;; birds – special
(defun ref-fish () ;; 참조 함수 선언
fish)
(defun ref-birds ()
birds)
(ref-fish) => (salmon tuna) ;; top level에서 실행
(ref-birds) => (eagle vulture)
(defun test-lexical (fish)
(list fish (ref-fish)))
> (test-lexical ’(guppy minnow)) ;; lexical 변수 참조
((GUPPY MINNOW) (SALMON TUNA)) ;; ref-fish함수 안의 fish는 lexical scoping에 따라 전역 변수 참조
(defun test-dynamic (birds)
(list birds (ref-birds))) ;; dynamic 변수 참조
> (test-dynamic ’(robin sparrow)) ;; ref-birds 함수 안의 birds는 dynamic 변수 bird 참조
((ROBIN SPARROW) (ROBIN SPARROW))
> (ref-birds)
(EAGLE VULTURE)
TEST-DYNAMIC의 body로 들어 갈 때 새로운 dynamic 변수 birds가 새기고 TEST-DYNAMIC을 떠날 때 까지 변수
birds는 이값을 참조 하게 됨
20. Advanced topics:
defvar, defparameter, defconstant
DEFVAR
초기 값 없이 선언 될 수 있음
(defvar *total-glasses*)
한번 그 값이 선언되면 defvar를 통해서 바꿀 수 없음
> (defvar *total-glasses* 0
"Total glasses sold so far")
> (defvar *total-glasses* 3
"Total glasses sold so far")
> *total-glasses*
0
DEFPARAMETER
초기 값과 함께 선언 되어야 함
(defparameter abc 10)
선언된 값을 defparameter를 통해 바꿀 수 있음
> (defparameter *max-glasses* 500
"Maximum number of glasses we can make")
> (defparameter *max-glasses* 300)
> *max-glasses*
300
defvar와 defparameter는 똑같은 문법 구조이지만 defparameter는 프로그램이 실행되는 동안 변화되지 않아야 하는 값에 사용
DEFCONSTANT
만들어진 상수는 절대 바뀌지 않음
Lisp의 special valiable들은 관습적으로 *로 둘러 싸서 표현 하지만 defconstant는 예외를 적용
Lisp는 PI와 같은 내장 상수들이 있음
21. Advanced topics:
Rebinding special variables
Function 인자
함수의 인자를 special 변수의 이름으로 지정하여 호출 시 rebinding
(defun print-in-base (*print-base* x)
(format t "~&~D is written ~S in base ~D."
x x *print-base*))
> (print-in-base 2 205)
205 is written 11001101 in base 2.
NIL
LET
LET을 통한 바인딩
(defvar *foo* 2)
(defun bump-foo ()
(incf *foo*))
(defun rebind-boo ()
(let ((*foo* 100))
(incf *foo*)
(bump-foo)))
23. Ansi Lisp:
Macro design – example(2)
ntimes 매크로
(defmacro ntimes (n &rest body)
`(do ((x 0 (+ x 1)))
((>= x ,n))
,@body))
문제점
변수 x를 생성해서 기존에 변수 x가 있었다면 의도치 않은 결과 발생
잘못된 예
(let ((x 10))
(ntimes 5
(setf x (+ x 1)))
x)
10
X에 10을 할당하고 그 값을 5번 증가 시켜야 하지만 결과 값은 그대로 10이 됨
매크로 확장
(let ((x 10))
(do ((x 0 (+ x 1)))
((>= x 5))
(setf x (+ x 1))) ;; let으로 생성된 x가 아닌 do안의 변수 x가 증가
x)
24. Ansi Lisp:
Macro design – example(3)
수정된 ntimes 매크로
do loop안의 변수를 gensym을 통하여 프로그램내의 어느 심볼과도 같지 않게 만듬
(defmacro ntimes (n &rest body)
(let ((g (gensym)))
`(do ((,g 0 (+ ,g 1)))
((>= ,g ,n))
,@body)))
문제점
반복 평가
do 문의 body가 평가 될때 마다 n이 새롭게 평가 된다.
잘못된 예
> (let ((v 10))
(ntimes (setf v (- v 1))
(princ ".")))
.....
NIL
원하는 결과는 v에서 10에서 1을 뺀 9개의 점을 출력
매크로 확장
(let ((v 10))
(do ((#:g1 0 (+ #:g1 1)))
((>= #:g1 (setf v (- v 1))))
(princ ".")))