2019년 9월 11일 수요일

[clojure programming] 2. Functional Programming - 로깅파일 만들기

출처 : Clojure programming
Building a Primitive Logging System. with Composable Higher-Order Functions

(defn print-logger [writer]
  #(binding [*out* writer]
     (println %)))

(def *out*-logger (print-logger *out*))
(*out*-logger "hello")

버퍼를 만들어 보자.
(def writer (java.io.StringWriter.))
(def retained-logger (print-logger writer))
(retained-logger "hello-1")
(retained-logger "hello-2")
(str writer)

파일에 써보자.
(require 'clojure.java.io)
(defn file-logger [file]
  #(with-open [f (clojure.java.io/writer file :append true)]
      ((print-logger f) %)))

(def log-file (file-logger "message.log"))
(log->file "HELLO FILE")
지금까지는 아주 적은 코드로 서로 다른 곳에 쓸 수 있는 방법을 알아봤다.
만약, 여러곳에 써야 한다면 어떻게 해야 할까. doseq를 쓴다.
(defn multi-logger
  [& logger-fns]
    #(doseq [f logger-fns]
       (f %)))
(def log (multi-logger
           (print-logger *out*)
           (file-logger "message.log")))
(log "hello console and file")

마지막 예제, 진짜 로깅인 것처럼 시간 포맷까지 앞에 붙여보자.
Adding a piece of "logging middleware" to include a timestamp with each log message
(defn timestamped-logger
  [logger]
  #(logger (format "[%1$tY-%1$tm-%1$ts %1$tH:%1$tM%1$tS] %2%s" (java.util.Date.) %)))

(def log-timestamped (timestamped-logger
                      (multi-logger
                        (print-logger *out*)
                        (file-logger "message.log"))))
(log-timestamped "goobye, now")

12. Generalized variable

챕터8에서 말했듯이 매크로의 장점 중 하나인 매개변수의 변화이다.
setf매크로를 보자. 이 챕터는 setf에 숨겨진 것들을 본다. 그리고 예지로 무엇을 할 수 있는지도.

setf를 이용하여 매크로를 만드는 건 어렵다. 섹션1에서 틀린 방식을 보여줄 것이고,
다음섹션에서 무엇이 문제인지, 어떻게 바꾸는지 본다.
마지막 섹션에서는 어떻게 setf의 반전(inversion)을 만드는지 보자.

12.1 The Concept
setf 매크로는 setfq의 일반화 버전이라 생각하자, setf에서는 단순변수가 아니라. 호출식일 수 있다!
(setf lst '(a b c))
(setf x y)는 x가 y로 평가되도록 처리해라(see to it that x evaluate y)로 이해될 수 있는데,
매크로에서, setf는 위 문장이(일반적으로) 참이 되도록 하기 위해서 매개변수의 내부를 볼 수 있다.(보아야한다)

첫번째 매개변수가 심볼이면, setf는 setq로 확장한다.
하지만 queryf라면 setf는 그 쿼리와 연관된 assertion(단정?)으로 확장한다.
두번째 매개변수는 상수가 당연하니까 아래처럼 될 것이다.
(progn (rplaca lst 480) 480)
이렇게 쿼리에서 assertion(rplaca)로 변화하는 것을 inversion이라 한다.
커먼리습에 정의된 대부분 access함수들은 이미 정해진 inversion이 있다.
car, cdr, nth, aref, get, gethash...
setf의 첫번째 매개변수에 들어올 수 있는 표현식을 generalized variable이라 부른다.
이 setf를 이용한 매크로 예를 보자.
(defmacro toggle (obj) ;; 틀림
  `(setf ,obj (not ,obj)))
이 녀석ㅇ느 generalized variable을 toggle하는 매크로다.
(let ((lst '(a b c)))
  (toggle (car lst))
  lst)
(NIL B C)
다음으로 넘어가자. 작은 마을에서 거주자들끼리 관계를 DB에 저장하고자 한다.
사람들의 친구를 기록하는 테이블을 만들어보자.
(defvar *friends* (make-hash-table))
; 이 hash-table 안에 각 사람들의 친구들이 있는데 이것 또한 hash-table이다.
(setf (gethash 'mary *friends*) (make-hash-table))
; John을 Mary의 친구로 만들자.
(setf (gethash 'john (gethash 'mary *friends*)) t)
마을은 이제 두 파벌로 나눴다(친구가 있다 없다). 이 말은 양쪽 일수도 둘다 아닐 수도 없다.
둘 중 하나이다. (왠지 toggle이 유용해보임)
친구가 이다/아니다를 toggle할 것이다.
(setf (gethash x (gethash y *friends*))
      (not (gethash x (gethash y *friends*))))
; 꽤나 복잡한데 setf가 없이 더 편리하게 만들 수 있다.
; 일단 아래처럼 db로 접근하는 매크로를 만든다면
(defmacro friend-of (p g)
  `(gethash ,p (gethash ,g *friends*)))
(toggle (friend-of x y))
; 이렇게하니 꽤나 이쁘다. 그런데 문제가 있다고? 아래 섹션을 보자.

12.2 다중평가문제
toggle을 다시보자
(defmacro toggle (obj) ; wrong
  `(setf ,obj (not ,obj)))
이 문제는 10.1에서 대중평가와 동일하다.
사이드이펙트가 있는 경우 두번실행되면서 문제가 대두됨.
(toggle (nth incf i) lst)
; 여기에서 incf가 여러번 일어나면 그 숫자는 달라질 것!
(setf (nth (incf i) lst)
      (not (nth (incf i) lst)))
; i가 두번 증가한다. 하여!
(let ((lst '(t nil t))
      (i -1))
  (toggle (nth incf i) lst))
  lst)
(T NIL T)
?? 바뀐게 없는데 왜 그러지? 자세히 보자.
(setf (nth 0 lst)
      (not (nth 1 lst)))
두번째 값의 toggle을 첫번째에 덮은 것이다...

우린 이제 표현식 내부에서 뭘 하는지 봐야한다. 하나하나 분리해서 사이드이팩트가 있는지 보고 따로 평가해야한다.
이걸 쉽게하기 위해 커먼리습에서 define-modify-macro를 제공하고 얘가 대신 해준다.
이녀석은 3개의 매개변수를 받는데
1. name of macro
2. its additional parameters (after the generalized variable)
3. name of the function (which yeilds the new value for the generalized variable)
이제 toggle 매크로를 다시 정의하자
(define-modify-macro toggle () not)
; toggle : 이름
; ()  매개변수 (두번째 매개변수 부터라고 생각하면 된다)
; 함수 (generalized variable을 가져와서 새로운 값을 리턴하면 그것으로 재세팅됨)
여기 코드의 뜻은
(toggle place)를 평가하려면, place에서 특정 위치를 찾고, val이라는 값이 있으면 (not val)로 넣어서 그 리턴값을 세팅해라.
다시 평가하면 잘 나올 것.

이 toggle을 좀 더 일반화하자. (여러 매개변수를 넣을 수 있다면??!!)
; fig 12.1 Macros which operate on generalized variables.
(defmacro allf (val &rest args)
  (with-gensyms (gval)
    `(let ((,gval ,val))
       (setf ,@(mapcan #'(lambda (a) (list a gval))
                       args)))))

(defmacro nilf (&rest args) `(allf nil ,@args))

(defmacro tf (&rest args) `(allf t ,@args))

(defmacro toggle (&rest args)
  `(progn
     ,@(mapcar #'(lambda (a) `(toggle2 ,a))
               args)))

(define-modify-macro toggle2 () not)

12.3 New Utilities
이제 generalized variable을 이용한 유틸들을 보자.
Fig 12.1에 nilf가 특이하다
(setf x nil y nil z nil)
; 이녀석은 아래와 같다
(nilf x y z)
그림 12.2 리스트의 마지막을 파괴적으로 수정하는 매크로들이다.
Section 3.1에서 (nconc x y)보다는 (setq (nconc x y))를 쓰라고 한다.
둘 다 같은 일을 하지만 뒤에 내용이 명시적으로 잘 보이기 때문인가 보다.
이제 이 형식을 매크로로 만든 것이 12.2의 concf다.
(define-modify-macro concf (obj) nconc)
; concf 이름
; (obj) 매개변수 (기본 매개변수 place뒤에 obj, (concf place obj)이렇게 쓰는 것
; nconc 가져온 값을 이거로 치환
; obj는 여기서 y로 보임. obj는 generalized variable 뒤에 오는 객체랬으니까
; y역할을 하는 것이 맞다. 왜 이렇게 나눠놓았을까 생각해봤는데
; 아마 generalized variable(값이 바뀌어야 하는 변수)는 따로 다루는게 아닌가 싶고
; 당연히 다루려고 해당 매크로를 쓰는 거니까 상관없다고 생각했을 수도.

;; 그리고 conc1f는 list 뒤에 요소 하나를 추가하는 녀석이다.
(define-modify-macro conc1f (obj)
    (lambda (place obj)
            (nconc place (list obj))))
; 역시 두개를 받는 람다를 만들어서
; nconc를 했다. 그리고 place 뒤에 (list obj)를 붙였다.
; 저절로 place에 저장될 것!

(define-modify-macro concnew (obj &rest args)
    (lambda (place obj &rest args)
            (unless (apply #'member obj place args)
                (nconc place (list obj)))))
; concew는 conc1f와 같지만 멤버가 없을 때만 넣는 것.
; args는 member를 위한 다른 equals 방식이라던가 그런 설정값이다.

[on lisp] 11.5~6 클로저와 매크로의 차이

mvpsetq는 3개의 유틸리티 함수를 사용한다.
(defun mklist (obj)
  (if (listp obj) obj (list obj)))

(defun group (source n)
  (if (zerop n) (error "zero length"))
  (labels ((rec (source acc)
             (let ((rest (nthcdr n source)))
               (if (consp rest)
                   (rec rest (cons (subseq source 0 n) acc))
                   (nreverse (cons source acc))))))
    (if source (rec source nil) nil)))
(defun shuffle (x y)
  (cond ((null x) y)
        ((null y) x)
        (t (list* (car x) (car y) 
                  (shuffle (cdr x) (cdr y))))))
shuffle을 한번 써보자
(shuffle '(a b c) '(1 2 3 4 5))
(A 1 B 2 C 3 4 5) 
mvpsetq로 그림 11.13의 mvdo를 정의할 수 있다.
condlet처럼 이 매크로는 mapcar대신 mappend를 쓰는데 이유는 오리지널 매크로 호출의 변경을 바지하기 위함이다.
mappend-mklist 는 트리를 1레벨로 펼친다
(defun mappend (fn &rest lsts)
  (apply #'append (apply #'mapcar fn lsts)))

(mappend #'mklist '((a b c) d (e (f g) h) ((i)) j))

mvpsetq를 보자.
(defmacro mvpsetq (&rest args)
  (let* ((pairs (group args 2))
         (syms  (mapcar #'(lambda (p)
                            (mapcar #'(lambda (x) (gensym))
                                    (mklist (car p))))
                        pairs)))
    (labels ((rec (ps ss)
               (if (null ps)
                   `(setq
                     ,@(mapcan #'(lambda (p s)
                                   (shuffle (mklist (car p)) 
                                            s))
                               pairs syms))
                   (let ((body (rec (cdr ps) (cdr ss))))
                     (let ((var/s (caar ps))
                           (expr (cadar ps)))
                       (if (consp var/s)
                           `(multiple-value-bind ,(car ss) 
                                                 ,expr
                              ,body)
                           `(let ((,@(car ss) ,expr))
                              ,body)))))))
      (rec pairs syms))))
이걸 한번 매크로확장해보자.
(print 
 (macroexpand
  '(mvpsetq a 10 b 20 c 30 d 40)))
(LET ((#:G1 10))
 (LET ((#:G2 20))
  (LET ((#:G3 30))
   (LET ((#:G4 40)) (SETQ A #:G1 B #:G2 C #:G3 D #:G4))))) 
이렇게 LET으로 (gensym)을 하고 한번에 setq를 한다.

그런데 psetq랑 뭐가 다른거지?
(macroexpand
  ' (psetq a 1 b a c 3))
(LET ((#:PSETQ-1 1) (#:PSETQ-2 A) (#:PSETQ-3 3)) ; 밸류들
  (SETQ A #:PSETQ-1) (SETQ B #:PSETQ-2) (SETQ C #:PSETQ-3) NIL) 

(setf a 10)
(psetq a 1 b a c 3)
(print b) ; 10

그런데 어떻게 확장되는 걸까
1. args를 2그룹으로 나눈다.
2. 갯수만큼 (gensym)을 만든다.
(group '(a 10 b 20 c 30 d 40) 2)
((A 10) (B 20) (C 30) (D 40)) 

(mapcar #'(lambda (p)
    (mapcar #'(lambda (x) (gensym))
                (mklist (car p))))
    '((A 10) (B 20) (C 30) (D 40)))
((#:G3210) (#:G3211) (#:G3212) (#:G3213)) 
아래 함수를 이해해보자.
#'(lambda (p s) (shuffle (mklist (car p)) s))
mklist는 리스프로 만드는 함수 리스트면 그대로 뱉고 리스트가 아니면 감싼다.
shuffle은 위에 사용법이 있다 지그재그로 첫번째,두번째 매개변수를 합친다
p는 pairs이며, syms는 심볼인듯.(gensym)값
즉 pairs와 syms를 받아서 p의 첫번째 값을 gensym에 하나씩 넣어준다.
(labels ((rec (ps ss) ;; 내부함수에 label로 이름 지정, 매개변수 지정
  (if (null ps) ; ps가 이제 없다면
    `(setq ; (SETQ A #:G1 B #:G2 C #:G3 D #:G4) 부분을 만듬.
       ,@(mapcan #'(lambda (p s) (shuffle (mklist (car p)) s))
                 pairs syms))
     (let ((body (rec (cdr ps) (cdr ss))))
       (let ((var/s (caar ps))
             (expr (cadar ps)))
       (if (consp var/s)
         `(multiple-value-bind ,(car ss) 
                               ,expr
                               ,body)
         `(let ((,@(car ss) ,expr))
               ,body)))))))
 (rec pairs syms))
이제 다음으로 넘어가자. mvdo는 mvdo*를 봤으니 넘어가자.

11.6 Need for Macros
이렇게 평가를 잠시 미루도록 할 수 있는 건 매크로만의 전유물은 아니다.
함수를 closure로 감싸는 방법이 있다.
조건절 혹은 반복 평가들 모두 이런 걸로 가능하게 된다.
예를들어 if매크로를 아래처럼 바꿀 수 있다.
(defun fnif (test then &optional else)
  (if test
      (funcall then)
      (if else (funcall else))))
이걸 사용하려면 기존처럼 사용하면 안된다.
(if (rich) (g-sailing) (rob-bank))
여기서
(fninf (rich)
  #'(lambda () (go-sailing))
  #'(lambda () (rob-bank)))
이렇게 쓰면 된다. 만약 우리가 원하는 것이 조건평가만이라면 매크로는 전혀 쓸모 없다.
이것들은 그저 프로그램을 깨끗하게 하는 녀석들일 것이다. 하지만 매크로는 우리가 아규먼트 폼을 빼낼때나, 매개변수를 바인딩 한단거나 하는 일에서 필수이다.
반복에도 동일한 논리가 적용된다. 오로지 반복만을 위해서는 매크로는 필요 없다.
dolist 같은 걸 보자. 이걸 대체하려면 mapc와 lambda를 이용하면 된다.
(dolist (b bananas)
  (peel b)
  (eat b))

(mapc #'(lambda (b)
           (peel b)
           (eat b))
      bananas)
forever도 함수로 적을 수 있다.
(defun forever (fn)
  (do ()
      (nil)
      (funcall fn)))
이렇게 바디도 감싸서 보낼 수 있다.
하지만 반복을 생성할 때는, 그저 반복만을 위해 만들지 않는다.(as forever does)
이것들은 대게 binding와 iteration의 조합을 위해서 쓴다.
함수에서는 이런 개의 유틸리티 함수를 사용한다.
(defun mklist (obj)
  (if (listp obj) obj (list obj)))

(defun group (source n)
  (if (zerop n) (error "zero length"))
  (labels ((rec (source acc)
             (let ((rest (nthcdr n source)))
               (if (consp rest)
                   (rec rest (cons (subseq source 0 n) acc))
                   (nreverse (cons source acc))))))
    (if source (rec source nil) nil)))
(defun shuffle (x y)
  (cond ((null x) y)
        ((null y) x)
        (t (list* (car x) (car y) 
                  (shuffle (cdr x) (cdr y))))))
shuffle을 한번 써보자
(shuffle '(a b c) '(1 2 3 4 5))
(A 1 B 2 C 3 4 5) 
mvpsetq로 그림 11.13의 mvdo를 정의할 수 있다.
condlet처럼 이 매크로는 mapcar대신 mappend를 쓰는데 이유는 오리지널 매크로 호출의 변경을 바지하기 위함이다.
mappend-mklist 는 트리를 1레벨로 펼친다
(defun mappend (fn &rest lsts)
  (apply #'append (apply #'mapcar fn lsts)))

(mappend #'mklist '((a b c) d (e (f g) h) ((i)) j))

mvpsetq를 보자.
(defmacro mvpsetq (&rest args)
  (let* ((pairs (group args 2))
         (syms  (mapcar #'(lambda (p)
                            (mapcar #'(lambda (x) (gensym))
                                    (mklist (car p))))
                        pairs)))
    (labels ((rec (ps ss)
               (if (null ps)
                   `(setq
                     ,@(mapcan #'(lambda (p s)
                                   (shuffle (mklist (car p)) 
                                            s))
                               pairs syms))
                   (let ((body (rec (cdr ps) (cdr ss))))
                     (let ((var/s (caar ps))
                           (expr (cadar ps)))
                       (if (consp var/s)
                           `(multiple-value-bind ,(car ss) 
                                                 ,expr
                              ,body)
                           `(let ((,@(car ss) ,expr))
                              ,body)))))))
      (rec pairs syms))))
이걸 한번 매크로확장해보자.
(print 
 (macroexpand
  '(mvpsetq a 10 b 20 c 30 d 40)))
(LET ((#:G1 10))
 (LET ((#:G2 20))
  (LET ((#:G3 30))
   (LET ((#:G4 40)) (SETQ A #:G1 B #:G2 C #:G3 D #:G4))))) 
이렇게 LET으로 (gensym)을 하고 한번에 setq를 한다.

그런데 psetq랑 뭐가 다른거지?
(macroexpand
  ' (psetq a 1 b a c 3))
(LET ((#:PSETQ-1 1) (#:PSETQ-2 A) (#:PSETQ-3 3)) ; 밸류들
  (SETQ A #:PSETQ-1) (SETQ B #:PSETQ-2) (SETQ C #:PSETQ-3) NIL) 

(setf a 10)
(psetq a 1 b a c 3)
(print b) ; 10

그런데 어떻게 확장되는 걸까
1. args를 2그룹으로 나눈다.
2. 갯수만큼 (gensym)을 만든다.
(group '(a 10 b 20 c 30 d 40) 2)
((A 10) (B 20) (C 30) (D 40)) 

(mapcar #'(lambda (p)
    (mapcar #'(lambda (x) (gensym))
                (mklist (car p))))
    '((A 10) (B 20) (C 30) (D 40)))
((#:G3210) (#:G3211) (#:G3212) (#:G3213)) 
아래 함수를 이해해보자.
#'(lambda (p s) (shuffle (mklist (car p)) s))
mklist는 리스프로 만드는 함수 리스트면 그대로 뱉고 리스트가 아니면 감싼다.
shuffle은 위에 사용법이 있다 지그재그로 첫번째,두번째 매개변수를 합친다
p는 pairs이며, syms는 심볼인듯.(gensym)값
즉 pairs와 syms를 받아서 p의 첫번째 값을 gensym에 하나씩 넣어준다.
(labels ((rec (ps ss) ;; 내부함수에 label로 이름 지정, 매개변수 지정
  (if (null ps) ; ps가 이제 없다면
    `(setq ; (SETQ A #:G1 B #:G2 C #:G3 D #:G4) 부분을 만듬.
       ,@(mapcan #'(lambda (p s) (shuffle (mklist (car p)) s))
                 pairs syms))
     (let ((body (rec (cdr ps) (cdr ss))))
       (let ((var/s (caar ps))
             (expr (cadar ps)))
       (if (consp var/s)
         `(multiple-value-bind ,(car ss) 
                               ,expr
                               ,body)
         `(let ((,@(car ss) ,expr))
               ,body)))))))
 (rec pairs syms))
이제 다음으로 넘어가자. mvdo는 mvdo*를 봤으니 넘어가자.

11.6 Need for Macros
이렇게 평가를 잠시 미루도록 할 수 있는 건 매크로만의 전유물은 아니다.
함수를 closure로 감싸는 방법이 있다.
조건절 혹은 반복 평가들 모두 이런 걸로 가능하게 된다.
예를들어 if매크로를 아래처럼 바꿀 수 있다.
(defun fnif (test then &optional else)
  (if test
      (funcall then)
      (if else (funcall else))))
이걸 사용하려면 기존처럼 사용하면 안된다.
(if (rich) (g-sailing) (rob-bank))
여기서
(fninf (rich)
  #'(lambda () (go-sailing))
  #'(lambda () (rob-bank)))
이렇게 쓰면 된다. 만약 우리가 원하는 것이 조건평가만이라면 매크로는 전혀 쓸모 없다.
이것들은 그저 프로그램을 깨끗하게 하는 녀석들일 것이다. 하지만 매크로는 우리가 아규먼트 폼을 빼낼때나, 매개변수를 바인딩 한단거나 하는 일에서 필수이다.
반복에도 동일한 논리가 적용된다. 오로지 반복만을 위해서는 매크로는 필요 없다.
dolist 같은 걸 보자. 이걸 대체하려면 mapc와 lambda를 이용하면 된다.
(dolist (b bananas)
  (peel b)
  (eat b))

(mapc #'(lambda (b)
           (peel b)
           (eat b))
      bananas)
forever도 함수로 적을 수 있다.
(defun forever (fn)
  (do ()
      (nil)
      (funcall fn)))
이렇게 바디도 감싸서 보낼 수 있다.
하지만 반복을 생성할 때는, 그저 반복만을 위해 만들지 않는다.(as forever does)
이것들은 대게 binding와 iteration의 조합을 위해서 쓴다.
함수에서는 이런 바인딩에서는 제한적이다.
만약 당신이 연속적인 리스트 요소를 하나씩 차례대로 반복하면서 바인딩 하고 싶다면, mapping 함수를 쓸 수 있다.
하지만, 만약 요구사항이 이것보다 조금만 복잡해지면, 매크로를 써야 한

[on lisp] 12.3 new utilites

;; 12.3 New Utilities
; 이번에는 generalized variables에 관련된 새로운 유틸리티들을 볼 예정.
; 인수를 그대로 [setf]에 전달하려면 매크로 여야함.
; Fig 12.1는 setf을 이용한 예제를 보여줌.

(setf x nil y nil z nil)
;; 이럴거면 이제
(nilf x y z)
;; 이렇게 쓸 수 있다.
;; 이 4개의 매크로는 꽤나 중요한 점을 보여준다. 바로 값을 할당하는 연산자를 보여준 것이다.
;; Even if we only intent to use an operator on ordinary variables, it's worth wrting it to expand into a [A] instead of a [B]
; 일반 변수에만 연산자를 사용하려는 경우에도, setq보단 setf로 확장하도록 작성하는 것이 좋다.
; setq는 set qoute의 약자며 setf는 set function의 약자다
; q는 저장될 첫번째 매개변수로 이름만 올 수 있지만, setf는 함수도 올 수 잇다.

; Fig 12.2 에는 리스트의 끝을 파괴적으로 수정하기위한 3개의 매크로가 포함되어 있다.
; nconc란 사이드이펙트를 일으키는 conc (합치기) 첫번째 매개변수에 값이 더해지는 방식 (새로운 객체를 리턴하는게 아니라)

;; Fig 12.2 List operations on generalized variables.
(define-modify-macro concf (obj) nconc)

(define-modify-macro conc1f (obj)
(lambda (place obj)
(nconc place (list obj))))

(define-modify-macro concnew (obj &rest args)
(lambda (place obj &rest args)
(unless (apply #'member obj place args)
(nconc place (list obj)))))

; 우리는 이전에 (nconc x y)가 사이드이팩트를 일으킨다고 이야기 했다. 그리고 아래처럼 작성해야 한다.
(setq x (nconc x y))
;; 이런 형태를 매크로로 만드는 것이다. 그것이 concf다.
; conc1f는 새로운 요소를 리스트 뒤에 붙인다.
; concnew는 conc1f와 같지만 오로지 멤버에 없을 경우만이다.

;; 섹션 2.2에서 함수는 lambda-expression일 뿐만 아니라 symbol이 될 수 있다 했다.
; Thus it is find to give a whole lambda-expression as the third argument to [define-modify-macro], as in the definition of [conc1f]
; 따라서 define-modify-macro의 세번째 매개변수로 람다표현식 전체를 넣어도 된다.
; conc1 (45페이지)의 내용을 쓰자.
; page45 (Fig4.1 Small functions which operate on lists)
(defun conc1 (lst obj)
(nconc lst (list obj)))

(define-modify-macro conc1f (obj) conc1)
; conc1f라는 이름에
; define-modify-macro는 3개의 매개변수를 받는다.(다시씀)
; 1, name of the macro
;2. its additional parameters (after the generalized variable),
;3. the name of the function which yields the new value for the generalized variable

[on lisp] 12.2 The Multiple Evaluation Problem

;gnu clisp 2.49

(print "Hello, world!")

(defmacro toggle (obj)
    `(setf ,obj (not ,obj)))


(print
;; 빡치게 두번 실행됨. 한번만 실행되어야 하는데
;; 토글 안에서 obj를 두개 넣기 때문임.
(let ((lst '(t nil t))
      (i -1))
     (toggle (nth (incf i) lst))
     lst)
 )
(T NIL T) ; 안됨 첫번째 T가 NIL이 되어야함.
;; 형태를 더 보면
(setf (nth (incf i) lst)
      (not (nth (incf i) lst)))
;; 이렇게 (incf i)를 두번 부르게 된다.

;; 'toggle'의 표현식 인수를 단순히 'setf'의 첫 번째 인수로 삽입하는 것만으로는 충분하지 않다.

; 우리는 들어온 표현식의 내부를 확인해봐야 한다.
; 하위 양식이 포함 된 경우, 부작용이있는 경우 하위 양식을 분리하여 개별적으로 평가해야합니다.

; 쉽게 만들기 위해. Common Lisp는 setf에서 제한된 매크로 클래스를 자동으로 정의하는 매크로를 제공.
; 이 매크로가 setf의 제한된 매크로 클래스 인 'defined-modify-macro'.
; 이름, 추가적인 매개변수(after the generalized variable), 그리고 함수이름 3개의 매개변수를 받는다.

; Using define-modify-macro, we could define toggle as follows:
; (define-modify-macro toggle () not)
; 이 코드는 "to evaluate an expression of the form (toggle place), find the location specified by place,
; and if the value stored there is val, replace it with the value of (not val)"이다.
; (toggle place)형태의 표현식을 평가하려면, place로 특정되는 위치를 찾고, 값이 있다면 그걸 val로 하자, 그 val을 (not value)로 치환하라.
(define-modify-macro toggle () not)
;; 이렇게만 하면 된다. 테스트해보자.
(let ((lst '(t nil t))
      (i -1))
     (toggle (nth (incf i) lst))
     lst)
(NIL NIL T) ; 잘됨

;; 좀더 일반화 할 수 있다.
;setf와 setq는 임의의 갯수의 인수를 취할 수 있으므로 toggle도 마찬가지로 가능할 것이다.
; modify-macro 위에 다른 매크로를 정의하여이 기능을 추가 할 수 있습니다.
;Fig 12.1로 더 자세히 보자.
(defmacro allf (val &rest args)
    (with-gensyms (gval)
        `(let ((,gval ,val))
              (set ,@(mapcan #'(lambda (a) (list a gval))
                             args)))))

(defmacro nilf (&rest args) `(allf nil ,@args))

(defmacro tf (&rest args) `(allf t ,@args))

(defmacro toggle (&rest args)
    `(progn
         ,@(mapcar #'(lambda (a) '(toggle2 ,a))
                   args)))
(define-modify-macro toggle2 () not)
;; 이건 이전에 만든 토글
(defmacro toggle (obj)
    `(setf ,obj (not ,obj)))

[on lisp] 11.5 Iteration with Multiple Values (mvdo*)

11.5 Iteration with Multiple Values
fig 11.10을 보자
(defmacro mvdo* (parm-cl test-cl &body body)
  (mvdo-gen parm-cl parm-cl test-cl body))

(defun mvdo-gen (binds rebinds test body)
  (if (null binds)
      (let ((label (gensym)))
        `(prog nil
           ,label
           (if ,(car test)
               (return (progn ,@(cdr test))))
           ,@body
           ,@(mvdo-rebind-gen rebinds)
           (go ,label)))
      (let ((rec (mvdo-gen (cdr binds) rebinds test body)))
        (let ((var/s (caar binds)) (expr (cadar binds)))
          (if (atom var/s)
              `(let ((,var/s ,expr)) ,rec)
              `(multiple-value-bind ,var/s ,expr ,rec))))))

(defun mvdo-rebind-gen (rebinds)
  (cond ((null rebinds) nil)
        ((< (length (car rebinds)) 3)
         (mvdo-rebind-gen (cdr rebinds)))
        (t
         (cons (list (if (atom (caar rebinds))
                         'setq
                         'multiple-value-setq)
                     (caar rebinds)
                     (third (car rebinds)))
               (mvdo-rebind-gen (cdr rebinds))))))
mvdo*를 써보자
(mvdo* ((x 1 (1+ x))
        ((y z) (values 0 0) (values z x)))
       ((> x 5) (list x y z))
  (princ (list x y z)))
(1 0 0)(2 0 2)(3 2 3)(4 3 4)(5 4 5)
(6 4 5)
와... 이렇게 여러개 반복이 멋지게 된단 말이다. 이런 식의 반복은 꽤나 유용하다. 그래픽 프로그램에서는 종종 좌표나 지역 같은 다중 값들을 한번에 다뤄야 한다. 단순한 대화형(반응형) 게임을 작성한다 할때, 목표는 두 물체 사이에 찌그러지지 않도록 하는 것이다. 만약 두 물체가 동시에 당신을 맞추면 지는 것. 혹은 두 물체가 서로를 부신다면 이기는 것. 그림 11.11 에서 이런 게임을 만들 때 mvdo*가 얼마나 유용할지 보여줄 것.
(mvdo* (((px py) (pos player) (move player mx my))
        ((x1 y1) (pos obj1)   (move obj1 (- px x1)
                                         (- py y1)))
        ((x2 y2) (pos obj2)   (move obj2 (- px x2)
                                         (- py y2)))
        ((mx my) (mouse-vector) (mouse-vector))
        (win     nil          (touch obj1 obj2))
        (lose    nil          (and (touch obj1 player)
                                   (touch obj2 player))))
       ((or win lose) (if win 'win 'lose))
   (clear)
   (draw obj1)
   (draw obj2)
   (draw player)) 
; (pos obj) return two values x, y representing the position of obj. ?Initially, the three objects have random positions
; (move obj dx dy) moves the obj depending on its type and the vector(dx,dy). returns two values x,y indicating the new position.
; (mouse-vector) returns two values dx, dy indicating the current movement of the mouse.
; (touch obj1 obj2) returns true if obj1 and obj2 are touching
; (clear) clear the game region
; (draw obj) draws obj at its current position
아래는 코드를 분석해본 것
(defmacro mvdo* (parm-cl test-cl &body body)
  (mvdo-gen parm-cl parm-cl test-cl body))

(defun mvdo-gen (binds rebinds test body)
  (if (null binds) ; binds가 없다면(재귀 다돌면)
      (let ((label (gensym)))
        `(prog nil
           ,label
           (if ,(car test)
               (return (progn ,@(cdr test))))
           ,@body
           ,@(mvdo-rebind-gen rebinds)
           (go ,label)))
      (let ((rec (mvdo-gen (cdr binds) rebinds test body)))  ;; binds가 있다면. (mvdo-gen (cdr binds) rebinds test body)를 rec로 부른다. (재귀할건가봄)
           (let ((var/s (caar binds)) (expr (cadar binds)))  ;; binds의 첫번째의 심볼값을 var/s라 하고 그 옆에 있는 녀석을 expr로 넣는다.(초기값)
          (if (atom var/s)  ; var/s가 하나라면 (다중이 아니라면)
              `(let ((,var/s ,expr)) ,rec) ; (,var/s ,expr) 로 값을 바인딩함 그 후 rec를 실행 (mvdo*는 순서대로 실행되어야함 바인딩이 순서가 있음)
              `(multiple-value-bind ,var/s ,expr ,rec)))))) ; 여러개면 multiple-value-bind를 쓴다.  (multiple-value-bind (...) (...) rec) rec에서 let과 muliple-value-bind가 계속 만들어짐
; 결국 재귀를 하다가 (if (null binds)...) 에 다다르면
; (prog를 쓰고 (return (progn ,@(cdr test)))로 리턴할 값을 리턴함.
; cadar는 cdr후 car한것
; 데모
'(mvdo* ((x 1 (1+ x))
        ((y z) (values 0 0) (values z x)))
       ((> x 5) (list x y z))
   (princ (list x y z)))
))
;(LET ((X 1))
; (MULTIPLE-VALUE-BIND (Y Z) (VALUES 0 0)
;  (PROG NIL #:G3210 (IF (> X 5) (RETURN (PROGN (LIST X Y Z)))) ; PROG NIL(바인딩없음) #:G3210 고투문을 위한 이름
;   (PRINC (LIST X Y Z)) ; ,@body
;   (SETQ X (1+ X))  ; ,@(mvdo-rebind-gen rebinds)
;   (MULTIPLE-VALUE-SETQ (Y Z) (VALUES Z X)) (GO #:G3210))))  ; (go ,label)))


; rebinds가 nil이면 nil
; (< (length (car rebinds)) 3) 길이가 3보다 작으면(2개면 값설정만 3개면 루프돌때마다 업데이트) (mvdo-rebind-ged (cdr rebinds))
; 
(defun mvdo-rebind-gen (rebinds)
  (cond ((null rebinds) nil)
        ((< (length (car rebinds)) 3)
         (mvdo-rebind-gen (cdr rebinds)))
        (t
         (cons (list (if (atom (caar rebinds)) ; 바인딩될 값이 atom이면 setq
                         'setq
                         'multiple-value-setq) ; 여러개면 multiple-value-setq
                     (caar rebinds)  ; caar로 첫번째(현재바인딩문)의 첫번째 빼냄.(심볼)
                     (third (car rebinds))) ; 업데이트문 빼냄.
               (mvdo-rebind-gen (cdr rebinds)))))) ; cdr로 다음타자 재귀로 부름. 마지막에 cons로 합침.
; 데모
(print (macroexpand
        (mvdo-rebind-gen '((x 1 (1+ x))
        ((y z) (values 0 0) (values z x))))))
; ((SETQ X (1+ X)) (MULTIPLE-VALUE-SETQ (Y Z) (VALUES Z X))) 

'(print (caar '((y z) (values 0 0) (values z x))))

[on lisp] 11.1 ~ 11.4 Classic Macro (다시 정리)

(defun mappend (fn &rest lsts)
  "maps elements in list and finally appends all resulted lists."
  (apply #'append (apply #'mapcar fn lsts)))

(defmacro condlet (clauses &body body)
  (let ((bodfn (gensym))
        (vars (mapcar #'(lambda (v) (cons v (gensym)))
                      (remove-duplicates
                        (mapcar #'car 
                                (mappend #'cdr clauses))))))
    `(labels ((,bodfn ,(mapcar #'car vars)
                 ,@body))
       (cond ,@(mapcar #'(lambda (cl)
                           (condlet-clause vars cl bodfn))
                       clauses)))))

(defun condlet-clause (vars cl bodfn)
  `(,(car cl) (let ,(mapcar #'cdr vars)
                (let ,(condlet-binds vars cl)
                  (,bodfn ,@(mapcar #'cdr vars))))))


(defun condlet-binds (vars cl)
  (mapcar #'(lambda (bindform)
              (if (consp bindform)
                  (cons (cdr (assoc (car bindform) vars))
                        (cdr bindform))))
          (cdr cl)))
(print 
  (macroexpand-1 '(condlet (((= 1 2) (x (princ 'a)) (y (princ 'b)))
                          ((= 1 1) (y (princ 'c)) (x (princ 'd)))
                          (t       (x (princ 'e)) (z (princ 'f))))
                  (list x y z))))
(`((#:G3210 (Y X Z) (LIST X Y Z)))
 (COND
  ((= 1 2)
   (LET (#:G3211 #:G3212 #:G3213)
    (LET ((#:G3212 (PRINC 'A)) (#:G3211 (PRINC 'B)))
     (#:G3210 #:G3211 #:G3212 #:G3213))))
  ((= 1 1)
   (LET (#:G3211 #:G3212 #:G3213)
    (LET ((#:G3211 (PRINC 'C)) (#:G3212 (PRINC 'D)))
     (#:G3210 #:G3211 #:G3212 #:G3213))))
  (T
   (LET (#:G3211 #:G3212 #:G3213)
    (LET ((#:G3212 (PRINC 'E)) (#:G3213 (PRINC 'F)))
     (#:G3210 #:G3211 #:G3212 #:G3213)))))) 

(print 
  (macroexpand '(condlet (((= 1 2) (x 1) (y 1))
                          ((= 1 1) (y 2) (x 2)))
                  (list x y ))))

(LABELS ((#:G3210 (Y X) (LIST X Y))) ; condlet
 (COND ; condlet
  ((= 1 2) ; condlet-clause
   (LET (#:G3211 #:G3212) ; condlet-clause
    (LET ((#:G3212 1) (#:G3211 1)) ; condlet-clause, conlet-binds
      (#:G3210 #:G3211 #:G3212)))) ; condlet-clause
  ((= 1 1) ; condlet-clause
   (LET (#:G3211 #:G3212) ; condlet-clause
    (LET ((#:G3211 2) (#:G3212 2)) ; condlet-clause, condlet-binds
      (#:G3210 #:G3211 #:G3212)))))) l condlet-clause
condlet에서 vars는 각 심볼별로 gensym을 만든다. 심볼은 하나만 gensym으로 만들면 되니까 remove-duplicates으로 겹치는 것을 없앰
(setf my-val     
    (mapcar #'cdr
        ((lambda (clauses) 
            (mapcar #'(lambda (v) (cons v (gensym)))
                (remove-duplicates
                    (mapcar #'car
                            (mappend #'cdr clauses)))))
         '(((= 1 2) (x (princ 'a)) (y (princ 'b)))
           ((= 1 1) (y (princ 'c)) (x (princ 'd)))
           (t       (x (princ 'e)) (z (princ 'f)))))))
((Y . #:G3210) (X . #:G3211) (Z . #:G3212)) 
1. conlet : 필요한 심볼들에 (gensym)을 적용하고 (cond ...)의 형태를 만든다. ... 안에 넣을 형태를condlet-clause에게 만들도록 위임 (mapcar로 하나씩)
label을 붙이기 위해 bodfn에도 gensym을 만듬.

이제부터 중요한 것은 심볼대신 gensym 값들을 가지고 놀아서 매크로를 조작한다. 정말 꽤나 어렵다. 자세히보자.
바인딩이 끝났으면,label을 이용하여 함수를 하나 만든다. 바로 쓰지는 않고 이 label에 붙여진 (gensym)인 bodfn을 다음 함수에 던진다.
이 다음함수(condlet-clause)가 바인딩 후 람다를 실행하는 일까지 한다.

2. condlet-clause (조건절을 만들고 그 뒤에 필요한 심볼의 gensym을 LET에 넣고, 다음 LET에 선택된 심볼에 바인딩될 값을 condlet-binds에서 위임
조건절은 아래와 같다.
,(car cl)
필요한 심볼의 gensym값을 모두 넣는다.
(let ,(map #'cdr vars) 
   ...)
선택된 심볼에 들어갈 값을 conlet-binds가 하도록 한다.
; vars는 (x #:G1 y #:G2) 형태
; cl는 (조건절 바인딩) 형태
(let ,(condlet-binds vars cl) 
그 후 넘겨받은 bodfn(라벨함수)를 실행하고 그 매개변수로 바인딩된 심볼의 (gensym)을 넣는다.
(,bodfn ,@mapcar #'car vars)...

3. condlet-binds : 어떻게 바인딩 되는지 선택하고 그 형태를 만든다.
일단 mapcar를 이용하여 lambda를 실행하는 형태인데, (let ...) 안에 들어가는 리스트를 뱉으면 된다.
아래와 같은 형태다.
((#:G3211 2) (#:G3212 2))
먼저 assoc이 뭔지 보자
(setq values '((x . 100) (y . 200) (z . 50)))
(print (assoc 'y values))
(Y . 200) 

이제 조건절은 썼으니 필요없고 바인딩될 표현식만 필요하다.
mapcar의 두번째 매개변수로 이것들만 들어갈 것이다. 그리고 이것은 lambda에서 bindform이라는 이름으로 들어간다.
(cdr cl)
bindform으로 들어온 녀석이 cons인지(리스트인지) 확인한다. 대부분 cons일 것이나 아니면 nil을 뱉는것 같다.
#'(lambda (bindform)
    (if (consp bindform)
        ...
하이라이트다. 여기서는 bindform은 (x 100) 뭐 이런 형태로 이루어져 있을 것이다. 위 예제를 보자.
clauses에서 cl하나는 ((= 1 2) (x 1) (y 1)) 이걸 말한다.
여기서 (car cl)은 (= 1 2)
(cdr cl)은 ((x 1) (y 1))이다.
여기서 mapcar 에 람다로 들어오는 값은 각각 (x 1) 와 (y 1)가 따로 들어온다.
그렇다면 (car bindform)은 뭘까
(car '(x 1)) ; x 심볼을 가져옴
(assoc (car bindform) vars) ; 심볼을 가져와서 vars에 해당심볼(x)를 가진 리스트를 찾아낸다.
(cdr (assoc (car bindform) vars) ; (x #:G12) 형태를 받을 것인데 거기서 cdr로 (gensym)값을 해시맵처럼 가져온다.
(cons (cdr (assoc (car bindform) vars)) ; cons로 gensym값과 바인딩될 벨류를 연결하여 리스트를 만든다.
                  (cdr bindform)))) ; bindform은 (x 1)의 형태로 가져온다. (cdr bindform)은 1
; (#:G1 10) 뭐 이런 형태로 들어간다.
즉 mapcar의 개별 값을 알았으니 전체 값을 돌리면
'((#:G1 10) (#:G2 30))
이런 형태를 만들어질 것이다.

해석 끝

11.2 The with- Macro
두번째로 컨텍스트를 다루는 타입은 with-를 사용하는 매크로다. 넓은 시야로 보면, 컨텍스트는 하나의 세상 안에 있는 상태들이다. 이 상태들에는 특별한 변수들의 값, 자료구조의 내용물, 리스프 바깥 세상의 상태값들을 말한다.
이런 종류의 컨텍스트를 만들고자 한다면 매크로로 해야 한다. 그렇지 않으면 코드의 body들이 closure로 전부 감싸야 한다.
(with-open-file (s "dump" :direction :output)
  (princ 99 s))
표현식의 평가가 끝나면 "dump"파일은 저절로 닫힌다.
이런 연산자는 매크로로 정의되어야 한다. 왜냐하면 s를 바인딩 하기 때문이기도 하지만, 어짜피 새로운 컨텍스트에서 form(body)가 평가되어야 한다.
일반적으로 컨텍스트를 생성하는 매크로는 코드블록 안에다가 확장을 한 다음, 코드 앞 뒤로 할 일을 더한다.
일반적으로 코드가 body 뒤에 실행되면, 그 목적ㅇ느 시스템의 일관된 상태(실행이전 상태가 대부분일 듯)로 정리하는 것이다.
예를들어 with-open-file은 열어둔 파일을 닫아야 한다. 이런 경우, 일반적으로 unwind-protect로 context-creating매크로가 확장된다.

unwind-protect의 목적은 실행 중에 인터럽트가 발생해도 특정 표현식은 평가되도록 한다.
하나 이상의 매개변수를 받아서 순서대로 평가한다. 만약 모두 잘 진행되면 첫번째 매개변수를 리턴한다.
prog1처럼 prog1과의 차이는 error가 나더라도 나머지 매개변수가 평가되는 것이다.
(setq x 'a)
A

(unwind-protect
  (progn (princ "What error?")
         (error "This error."))
  (setq x 'b))
What error?
>> Error: This error.
자 setq가 실행되기 전에 에러를 던지게 했다.그런데 한번 x가 어떻게 되었나 보자.
x
B
with-open-file이 unwind-protect로 확장되기 때문에, 파일은 실행중에 에러가 나도 클로즈가 된다.

컨텍스트생성 매크로들은 대게 특정 앱을 위해 작성되어 진다. 예를 들어 다중원격 DB를 다루는 프로그램을 작성한다 하자.
프로그램은 한번에 한 DB랑 대화하며, 그 DB는 글로벌 *db*에 있다. DB에 연결하여 일을 하기 전에 락을 먼저 걸어야 한다. 그래야 다른 녀석이 동시에 사용할 수 없을 것이다.
만약 디비(db)에서 쿼리(q)에서 값을 원한다면, 아래처럼 짤 것이다.
(let ((temp *db*))
  (setq *db* db)
  (lock *db*)
  (prog1 (eval-query q)
         (release *db*)
         (setq *db* temp)))
매크로로 이 모든 장부를 숨길 수 있다. 아래 코드들을 보자.
; pure macro
(defmacro with-db (db &body body)
  (let ((temp (gensym)))
    `(let ((,temp *db*))
       (unwind-protect
         (progn
           (setq *db* ,db)
           (lock *db*)
           ,@body)
         (progn
           (release *db*)
           (setq *db* ,temp))))))

; Combination of macro and function:
(defmacro with-db (db &body body)
  (let ((gbod (gensym)))
    `(let ((,gbod #'(lambda () ,@body)))
       (declare (dynamic-extent ,gbod))
       (with-db-fn *db* ,db ,gbod))))

(defun with-db-fn (old-db new-db body)
  (unwind-protect
    (progn
      (setq *db* new-db)
      (lock *db*)
      (funcall body)) ; with-db-fn은 함수라서 일부러 람다로 보냄
    (progn
      (release *db*)
      (setq *db* old-db))))
복잡성이 올라가면 2번째 방식이 더 실용적이다.
CLTL2 COMMON LISP에서, dynamic-extent 선언은 효율적인 할당을 위해 body를 가지는 closure를 허용한다. (CLTL1은 안됨)
우리는 with-db-fn을 실행하는 동안만 closure가 필요하며, 이 선언으로 컴파일러가 스택에 이것을 위한 공간을 할당할 수 있도록 허용함.
이 공간은 가비지 컬렉터가 회수하지 않고 let식이 끝나면 자동으로 회수됨.

11.3 조건평가
조건절에 따라서 평가를 안하고 끝내는 수가 있다( 좋은 것)
(if t
    'a
    (/ x 0))
0으로 나누는 것은 에러이지만 'a를 뱉으면서 잘 동작하고 끝난다.
아래 if3는 if문에서 nil값도 따로 보는 것. if3에서 (nil)로 감싼 이유는 nil은 꽤나 모호한 의미를 가지기 때문이다.
; Fig 11.5 : Macros for conditional evaluation
(defmacro if3 (test t-case nil-case ?-case
  `(case ,test
     ((nil) ,nil-case)
     (?     ,?-case)
     (t     ,t-case)))

(defmacro nif (expr pos zero neg)
  (let ((g (gensym)))
    `(let ((,g ,expr))
       (cond ((plusp ,g) ,pos)
             ((zerop ,g) ,zero)
             (t ,neg)))))
nif(numeric if)는 숫자에 따라 리턴된다.
(mapcar #'(lambda (x) (nif x 'p 'z 'n))
        '(0 1 -1))
(Z P N)

그림 11.6은 조건평가를 이용한 다양한 매크로를 보여준다.
매크로 in은 효율적으로 값이 있는지 확인한다. 만약 in을 확장하면 아래처럼 보일 것이다.
(let ((x (foo)))
  (or (eql x (bar)) (eql x (baz))))

;; 하지만 member를 이용하면 아래처럼 이뻐찐다.
(member (foo) (list (bar) (baz)))
;; 하지만 효율적인가?
member는 두 측면에서 비효율적이다.
1 member가 검색하기 위해선 임시 리스트가 필요하다 (만드는 리소스)
2 이 임시 리스트를 만들기 위해 모든 리스트는 평가되어야 한다. 비록 몇 개는 필요 없어도 (필요없는 평가)
(foo)가 (bar)라면 (baz)는 평가할 필요가 없다.
우리는 이거 대신 좀 더 효율적인 추상화를 만들 수 있다. or을 이용하는 효율이면서 member와 같은 추상을 사용하는 매크로!
그것이 in 이다
(defmacro in (obj &rest choices)
  (let ((insym (gensym)))
    `(let ((,insym ,obj))
       (or ,@(mapcar #'(lambda (c) `(eql ,insym ,c))
                     choices)))))

(defmacro inq (obj &rest args)
  `(in ,obj ,@(mapcar #'(lambda (a) `',a)
                      args)))

(defmacro in-f (fn &rest choices)
  (let ((fnsym) (gensym)))
    `(let ((,fnsym ,fn))
       (or ,@(mapcar #'(lambda (c) `(funcall ,fnsym ,c))
                     choices)))))

(defmacro >case (expr &rest clauses)
  (let ((g (gensym)))
    `(let ((,g ,expr))
       (cond ,@(mapcar #'(lambda (cl) (>casex g cl))
                       clauses)))))

(defun >casex (g cl)
  (let ((key (car cl)) (rest (cdr cl)))
    (cond ((consp key) `((in ,g ,@key) ,@rest))
          ((inq key t otherwise) `(t ,@rest))
          (t (error "bad >case clause")))))
이제 in을 사용해보자
(in (foo) (bar) (baz))

(let ((#:g25 (foo)))
  (or (eql #:g25 (bar))
      (eql #:g25 (baz))))
inq(in queue)를 보자
(inq operator + - *)
;; expands into
(in operator '+ '- '*)

(member x (list a b) :test #'equal)
; can be duplicated by
(in-if #'(lambda (y) (equal x y)) a b)

(some #'oddp (list a b))
; becomes
(in-if #'oddp a b)

11.4Iteration 반복
때로 문제는 매개변수가 항상 평가되서가 아니다, 오히려 한번만 평가되는 경우가 문제인 경우가 있다.
표현식의 바디가 반복하려면 매크로 써야함
(defmacro forever (&body body)
  `(do ()
       (nil)
     ,@body))
단순한 반복예시로 일단 맛보자
; Fig 11.7 : Simple iteration macros.
(defmacro while (test &body body)
  `(do ()
       ((not ,test))
     ,@body))

(defmacro till (test &body body)
  `(do ()
       (,test)
     ,@body))

(defmacro for ((var start stop) &body body)
  (let ((gstop (gensym)))
    `(do ((,var ,start (1+ ,var))
          (,gstop ,stop))
         ((> ,var ,gstop))
       ,@body)))
이제 본편으로 들어가자. 그림 11.8에서 하나만 잘라서 봐보자
(defmacro do-tuples/o (params source &body body)
  (if parms
      (let ((src (gensym)))
        `(prog ((,src ,source))
           (mapc #'(lambda ,parms ,@body)
                 ,@(map0-n #'(lambda (n)
                               `(nthcdr ,n ,src))
                           (1- (length parms))))))))

(do-tuples/o (x y) '(a b c d)
  (princ (list x y)))
(A B)(B C)(C D)
NIL
이렇게 연결되는 것이다. 신기하다. 어덯게 펼쳐지는지 이야기 해보자.
; 
(macroexpand
  (do-tuples/o (x y) '(a b c d) (princ (list x y))))

(BLOCK NIL
 (LET ((#:G3215 '(A B C D)))
  (TAGBODY
   (MAPC #'(LAMBDA (X Y) (LIST X Y)) (NTHCDR 0 #:G3215) (NTHCDR 1 #:G3215)))))
TAGBOY는 라벨같은 거다. GOTO문처럼 이동을 하게 해주는 녀석인데 여기서는 안쓰는 듯하다. 생기기만 하고
NTHCDR은 첫번째 매개변수의 숫자만큼 까고 나머지 cdr 리스트를 리턴한다.

내가 단계단계 연필로 확장해본 걸 아래 적어놓겠다.
; 1
(if '(x y)
  (let ((src #:G1))
    `(prog ((#:G1 '(a b c d))
       (mapc #'(lambda (x y) (princ (list x y)))
             ,@(map0-n #'(lambda (n) '(nthcdr ,n ,src))
                       (1- (length parms)))))))
; parms 이 있는 경우
; src (gensym)으로 생성

; 2
(let ((src #:G1))
  `(prog ((#:G1 '(a b c d))
     (mapc #'(lambda (x y) (princ (list x y)))
           ,@('(a b c d) '(b c d)))...)

; 3
`(prog ((#:G1 '(a b c d))
   ...
   (princ (list a b))
   (princ (list b c))
   (princ (list c d))
  nil)
정말 매크로를 만드는 작업은 하나의 예술과도 같다.

[on lisp] 10 Other Macro Pitfalls

10.1 Number of Evaluations

; Fig 10.1 : Controlling argument evaluation.
; A correct version
(defmacro for ((var start stop) &body body)
  (let ((gstop (gensym)))
    `(do ((,var ,start (1+ ,var))
          (,gstop ,stop))
         ((> ,var ,gstop))
       ,@body)))

;; subject to multiple evaluations:
(defmacro for ((var start stop) &body body)
  `(do ((,var ,start (1+ ,var)))
       ((> ,var ,stop))
     ,@body))

;; Incorrect order of evaluation:
(defmacro for ((var start stop) &body body)
  (let ((gstop (gensym)))
    `(do ((,gstop ,stop)
          (,var ,start (1+ ,var)))
         ((> ,var ,gstop))
       ,@body)))
2번째 for문에는 버그가 있다.
; 무한에 가까운 출력
(let ((x 2))
  (for (i 1 (incf x))
    (princ i)))
stop 매개변수로 들어온 녀석이 각 루프를 돌 때마다 평가된다.
이 말은 (incf x)가 매번 평가되면서 끝나지 않는 것이다.
어떻게 다른애들은 괜찮고 얘는 괜찮지 않을까?
다른 애들은 어떻게 한번만 실행되고 안되는 것일까?
이 생각은 일단 접어두고

매개변수로 들어온 stop에 side-effect가 있다면, 결과값은 위처럼 문제가 생길 수 있다는 것이다.
for같은 매크로를 만들 때, 꼭 알아야 할 것이 있다. 매크로는 값을 받아서 실행하는 녀석이 아니다. 그럴거면 함수를 만들었을 것이다.
매크로는 표현식을 받는다. 그것도 자신의 리스트 자료구조로 말이다.
그리고 이 표현식이 확장시 있는 곳에 따라, 평가는 한번 이상 평가될 수 있다.

이 경우 값을 바인딩 해놓는 것이다. stop으로 들어오는 표현식을 미리 평가하여 값으로 받은 후 loop에는 해당 값이 들어가도록 하는 것이다.
위에 내용들 보면 gstop이 그런 역할을 하는 녀석.

매크로는 반복을 위한 녀석임이 확실하지 않으면, 매크로 호출에 나타나는 횟수만큼 정확하게 평가되도록 해야 한다.
이 경우가 적용되지 않는 명백한 사례가 있다: 커먼리습의 or에서 들어온 매개변수가 모든 평가되어야 한다면 꽤나 쓸모없는 녀석일 것이다.
그러나 그런 경우 유저는 얼마나 많은 평가가 일어날지 예상할 수 있는지 안다.
하지만 for의 두번째 경우는 그렇지 않다.
사용자는 stop표현식이 두번 이상 평가된다고 가정할 이유가 없으며, 실제로 평가할 이유도 없다. (그런 내용이 매크로를 호출할 때 보이지 않는다는 것이다)
바로 이 두번째 for문 매크로가 가장 실수를 많이하는 경우일 것이다. 단서가 보이지 않으니까.

setf를 이용한 매크로의 경우 Unintended multiple evaluation(의도치 않은 다중 평가)라는 아주 어려운 문제를 직면하는 경우가 많다.
커먼리습은 이런 매크로를 만드는 것이 쉽도록 여러가지 유틸리티를 제공한다. (챕터 12에서 보여줌)

10.2 Order of Evaluation(평가의 순서)
표현식 평가의 횟수만이 문제가 아니라, 평가의 순서가 문제를 일으킬 수 있다.
커먼리습 함수 호출에서, 매개변수는 왼쪽에서 오른쪽 순서로 평가된다.
(setq x 10) ; 10
(+ (setq x 3) x) ; 6
이렇게 다르다. 매크로도 동일한 일이 일어난다.
매크로는 일반적으로 매크로 호출을 할 때 보이는 순서와 동일한 순서로 내부적으로 평가되어야 한다.
아래 for문이 그 법칙을 무시한 녀석이다. 잘 보면 var이 첫번째 매개변수고 stop이 두번째 매개변수인데
뒤바뀌어 있다. 호출할 때는 이렇게 된다는 힌트가 전혀 없다. 다들 평가가 매개변수의 순서대로 될 것이라고 생각할 것이다.
(defmacro for ((var start stop) &body body)
  (let ((gstop (gensym)))
    `(do ((,gstop ,stop)
          (,var ,start (1+ ,var)))
         ((> ,var ,gstop))
       ,@body)))
하여 for문에는 작은 버그가 있는데, stop이 start보다 먼저 평가되는 것이다.
(let ((x 1))
  (for (i x (setq x 13))
    (princ i)))
13
NIL
보면 알겠지만, 123456789010111213 이렇게 보여야 하는데 (setq x 13)이 먼저 세팅되면서
루프가 돌 기회가 없게 된 것이다.

10.3 Non-functional Expanders
리스프에서는 매크로로 확장된 코드가 순수 함수적이길 바란다. 확장코드(expander code)는 매개변수말고는 영향을 받지 말아야 하며 리턴값 외에는 다른 영향을 주면 안된다.
컴파일된 코드의 매크로 호출은 런타임에 다시(또) 확장되지 않는다고 봐도 무방하다.
만약 그렇게 되지 않으면 커먼리습은 이 확장이라는 것이 언제, 어디서, 얼마나 자주 매크로 호출이 확장되는지 알 수 없게 된다.
이런 것들 중 하나로 매크로의 확장이 달라지면 에러로 간주한다. (상관없어야 한다)
아래를 보자.
(defmacro nil! (x)  ;;wrong
  (incf *nil!s*)
    `(setf ,x nil))
글로벌 값 *nil!s*가 정말 매크로가 호출될 때 1번씩 호출되서 카운팅 해줄까? 그렇게 생각하면 오산이다.
주어진 호출은 한번이상 확장될 수 있고, 종종 그렇게 된다.
소스코드를 변환을 수행할 프리프로세서는 일단 변환을 하기 전에 평가를 먼저 해서 변환을 할지 안할지를 결정한다.

다시 말하지만, 일반적으로 확장자코드는 인수 외의 것에 의존하면 안된다.
예를들어, 문자열에서 확장을 빌드하는 매크로가 있다하자.
이 매크로가 확장시 패키지가 무엇인지 그런건 가정하지 않도록 하자.
(defmacro string-call (opstring &rest args) ; wrong
  `(,(intern opstring) ,@args))

(defun our+ (x y) (+ x y))
OUR+
(string-call "OUR+" 2 3)
5
보면 intern을 이용하여 문자열을 가져와 연관된 심볼를 리턴한다. 하지만 우리가 optional package argument을 안쓰면(제거하면), 이건 현재 패키지에서 일어날 것이다.
따라서, 확장이 생성될 때 패키지에 의존한다. 해당 패키지에 our+가 표시되지 않으면 해당 내용은 nil을 리턴할 것이다.
이 말은 환경에 따라 값이 달라질 것이란 말임..

Miller and Benson's Lisp Style and Design에선 expander code안에 side-effect가 있는 것을 특별히 멍청한 예로 제시했다.

또 다른 문제가 있는데 그것은 &args에서 온다.
&rest 파라미터에 들어오는 값은 새로 만들어진 거라고 개런티할 수 없다.
결론만 먼저 말하면, &rest 파라미터로 오는 값을 파괴적으로 수정하면 안된다(값을 바꾸면 안대!)
이런 경우는 함수와 매크로 모두에게 해당하는 말이다. 함수로 쓸 때는 apply와 함께 쓸 때 문제가 나타난다.
(defun et-el (&rest args)
  (nconc args (list 'et 'al)))

(et-al 'smith 'jones)
; (SMITH JONES ET AL)
하지만 여기서 apply를 이용해서 호출하면 어떻게 될까. 이미 존재하는 자료구조를 바꾼다.
(setq greats '(l m))
(apply #'et-el greats)
; (l m et el)
greats
; (l m et el)

매크로에서는 문제가 더 심각해진다. 매크로에서 &rest를 변경한다면 매크로 호출을 변경하는 일이 생기게 된다.
이 말은 의도치 않게 매크로를 자기자신이 재작성하는 일이 벌어진다.
여기서 문제는 더 심각해진다. 이 일은 실제로 이미 존재하는 구현 위에서 일어난다.
만약 nconc를 &rest매개변수에 적용하는 매크로를 정의한다고 해보자.
(defmacro echo (&rest args)
  `',(nconc args (list 'amen))) ; `',(foo) is equivalent to `(quote ,(foo)).
그리고 이제 함수를 정의하고 호출한다.
(defun foo () (echo x))

(foo) ; (X AMEN AMEN)
(foo) ; (X AMEN AMEN AMEN)
뭐지 계속 foo가 바뀐다. 왜냐하면 각 매크로 확장이 foo의 정의를 변경하는 것이다.
바꿔보자.
(defmacro echo (&rest args)
  `'(,@args amen))
@(comman-at)은 append와 같기 때문에 파괴적이지 않다. 그러므로 안전하다.

그런데 이걸로 끝일까.
매크로에서는 &rest이것만 조심한다고 끝나는 것이 아니다.
아무 매크로 매개변수가 그렇게 될 수 있다. 그 녀석이 리스트라면
(defmacro crazy (expr) (nconc expr (list t)))

(defun foo () (crazy (list)))

(foo) ; (T T)

10.4 Recursion
함수를 재귀적으로 만드는 것은 자연스러운 일이다.
(defun our-length (x)
  (if (null x)
      0
      (1+ (our-length (cdr x)))))
(defun our-length (x)
  (do ((len 0 (1+ len))
       (y x (cdr y)))
      ((null y) len)))
첫번째는 재귀적이고 두번째는 아니다.
그런데 매크로에서는 그저 backquotes나 commas를 넣는다고 쉽게 재귀가 되지 않는다.
; Fig 10.2 : Mistaken analogy to a recursive function.
; this will work
(defun ntha (n lst)
  (if (= n 0)
      (car lst)
      (ntha (- n 1) (cdr lst))))

; this won't work
(defmacro nthb (n lst)
  `(if (= ,n 0)
       (car ,lst)
       (nthb (- ,n 1) (cdr ,lst))))
왜 nthb가 문제가 되는 걸까. 바로 확장할때마다 자신을 가지고 있기 때문인데
함수의 경우 확장을 하면 매개변수의 값이 평가가 되기 때문에 언제 멈출지 안다. 하지만 매크로는 형태만 있기 때문에 계속 무한 확장하는 수가 있다.
; (nthb x y) 를 확장하자.
(if (= x 0)
    (car y)
    (nthb (- x 1) (cdr y)))
; 위 녀석은 다시 확장된다.
(if (= x 0)
    (car y)
    (if (= (- x 1) 0)
        (car (cdr y))
        (nthb (- (- x 1) 1) (cdr (cdr y)))))
그럼 어떻게 해야 하나. 일단 재귀적으로 풀지 않으면 된다. 아래처럼
(defmacro nthc (n lst)
  `(do ((n2 ,n (1- n2))
        (lst2 ,lst (cdr lst2)))
       ((= n2 0) (car lst2))))

아래 다른 예시가 있다.
(defmacro nthd (n lst)
  `(nth-fn ,n ,lst))

(defun nth-fn (n lst)
  (if (= n 0)
      (car lst)
      (nth-fn (- n 1) (cdr lst))))

(defmacro nthe (n lst)
  `(labels ((nth-fn (n lst)
              (if (= n 0)
                  (car lst)
                  (nth-fn (- n 1) (cdr lst)))))
     (nth-fn ,n, lst)))
보면 알겠지만 재귀는 함수로 만들어서 그걸 사용하는 것이다.
혹은 labels를 이용하여 로컬 함수를 정의하여, 그 녀석을 실행하는 것이다.
결국 두 경우 모두 함수와 매크로를 더해서 실행하는 방식이다.
물론 매크로가 전부 확장 할 수는 없지만 어쨋든 재귀는 값을 평가하긴 해야 한다.
그러므로 재귀적으로 평가는 가능하게 만들 수는 있는 것이다.

매크로의 확장함수가 일반 리스프 함수이니까, 우리는 이걸로 재귀적으로 만들 수 있을 것이다.(그러니까 매크로의 확장이 매크로일 수 있겠냐 라는 것이다. 지금까지 안되었는데)
built-in or함수를 정의하면서 봐보자.
macro ora는 or-expand라는 재귀함수를 호출하여 확장을 생성한다.
orb도 같은 일을 한다. orb는 특이하게 값이 아니라 매크로에 대한 인수에서 반복한다 (??)
이렇게 되면 무한으로 펼쳐질 것 같지만, 하나의 매크로 확장단계에서 생성된 orb에 대한 호출은 다음 단계에서 let으로 대체된다(?) 하여 최종 확장에서는 let들의 중첩들에 지나지 않는다.
아래를 보자.
; (orb x y) 확장
(let ((g2 x))
  (if g2
      g2
      (let ((g3 y))
        (if g3 
            g3 
            nil))))
;; 여기서 마지막 nil이 있는데 거기서 재귀가 잘 멈춘것.
;; 아래는 코드
(defmacro ora (&rest args)
  (or-expand args))
(defun or-expand (args)
  (if (null args)
      nil
      (let ((sym (gensym)))
        `(let ((,sym ,(car args)))
           (if ,sym
               ,sym
               ,(or-expand (cdr args)))))))

(defmacro orb (&rest args)
  (if (null args)
      nil
      (let ((sym (gensym)))
        `(let ((,sym ,(car args)))
           (if ,sym
               ,sym
               (orb ,@(cdr args)))))))

[on lisp] 18. destructuring - 1

이 매크로를 이해하는데 꽤나 오랜 시간이 걸렸다...
정말 폴그레이엄 미친 사람이다. 어떻게 이런책을 쓰고 이런 기술을 무료로 풀 수가 있는 거지?
이정도의 기술은 아무것도 아니라는 것인가?

그 머리를 가지고 싶다.
다른 onlisp내용은 스킵을 해도 이렇게 어렵게 이해한 내용은 적어놔야겠다.
커먼리습으로 평생 코딩을 하지 않더라도 이런 내용을 익힌다는 것은 참으로 재미 있다.

한가지 문제는 챕터 18. destructuring이 이제 시작이라는 점이다.
이 챕터는 특이하게 내용이 너무 많다...
;; '(dbind (a b c) (list 1 2 3) (list a b c))
;; pat = (a b c), seq = (list 1 2 3), body = (list a b c)
;; 바인딩할 값들을 gseq로 설정한 후 
;; dbind-ex으로 표현식을 만드는데, 바인딩하는 형태는 destruc가 한다.
(defmacro dbind (pat seq &body body)
  (let ((gseq (gensym)))
    `(let ((,gseq ,seq))
       ,(dbind-ex (destruc pat gseq #'atom) body))))

; let 안에 들어갈 리스트를 생성한다.
; 1. pat이 없으면 nil를 던진다(재귀의 베이스케이스) 아마 (cons ... nil)로 생성될듯
; 2 rest를 바인딩한다. 자세한 내용은 아래 잇음.
; 3 rest가 하나만 리스트가 아닌 경우만 rest가 걸리는데
;   걸리면 `((,rest (subseq ,seq ,n))) 이거로 한번에 다! 연결해버린다. rest가 원래 그런거니까 다음 내용 다 바인딩하는 녀석
;   ((( (subseq (list 1 2 3) 1)) => ((A (2 3)) 
; 4.rest가 없으면 일반적인 바인딩이 시작된다. (재귀 시작)
; 5. 이제 (car pat)로 첫번째 심볼을 꺼낸다.
; 6. (rec (destruc (cdr pat) seq atom? (1+ n))) 로 재귀로 다음 녀석들을 재귀로 만들어 낸다.
;    그 다음에 뭐 일단 첫번째 녀석을 먼저 손봐보자 재귀를 하는 곳이 6번만이 아니다.
; 7. p가 하나가 아니라면? 첫번째 가져온 바인딩될 녀석이 또! 리스트라면? (destructuring중이다)
; 8. 일단 p가 하나인 경우를 보자. 
; 9. (cons `(,p (elt ,seq ,n)) rec) 중요한 것은 (cons A rec) 에서 A다 여기가 이번 호출에서 하는 일이다.
; (,p (elt ,seq ,n)) 라고 해보자. 첫번째 재귀라면 (A (elt (list 1 2 3) 0)) -> (A 1) 이렇게 될 것이다. 
; 두번째는 (B (elt (list 1 2 3) 1)) -> (B 2)가 될 것이다. elt가 위치의 값을 가져온다.
; 10. 그 값을 cons로 뒤에 재귀값과 더하는 것이다.
; 11. 드디어 왔다. 만약 p가 리스트라면!!
; 여기 새로운 seq를 바인딩하여 캡처링을 보호해야 한다. (var (gensym)) 으로 임의의 심볼을 만든다.
; 12. (cons (cons `(,var (elt ,seq ,n))
;                  (destruc p var atom?))
;           rec
; cons가 두개 인걸 보니 바인딩 리스트 안에 리스트를 또 만든 듯. 
; `(,var (elt ,seq ,n))는 (#:G2 (elt seq n))으로 현재 위치에 있는 바인딩 값을 바인딩 해서 재귀로 쓸 때 보낸다. 
; 아래테스트 코드에도 설명이 되어 있다.
(defun destruc (pat seq &optional (atom? #'atom) (n 0))
  (if (null pat) ; 1
      nil
      (let ((rest (cond ((funcall atom? pat) pat) ; 2 처음들어오면 여긴 아님.
                        ((eq (car pat) '&rest) (cadr pat)) ; 3 일단 예시에서는 없음
                        ((eq (car pat) '&body) (cadr pat)) ; 4 여기도 아님.
                        (t nil)))) ; 대부분 리스트로 들어올테니 여기 nil이 된다.
       (if rest ; pat가 atom 이거나 &rest,&body가 아니라 '(a b c)로 들어오면 nil임.
           `((,rest (subseq ,seq ,n))) ; ((A (subseq (list 1 2 3) 1)) => ((A (2 3)) 
           (let ((p (car pat)) ; 4.5 리스트의 첫번째 녀석을 
                 (rec (destruc (cdr pat) seq atom? (1+ n)))) ; 6
             (if (funcall atom? p) ; 
                 (cons `(,p (elt ,seq ,n)) ;9  여기가 중요하다. p는 파라미터 심볼 하나고 elt로 seq의 n번재 녀석을 가져온다는 뜻이다. 그러므로 여기서는 바인딩만 가져오는 것.
                       rec) ; 10
                 (let ((var (gensym))) ; 11
                   (cons (cons `(,var (elt ,seq ,n)) ; 12
                               (destruc p var atom?)); 
                         rec))))))))

; destruc로 만든 코드를 dbind-ex에 binds라는 이름으로 들어온다.
; 바인드가 있으면,
; (let ...)안에 들어가게 되는데 바로 들어가는 것은 아니고
; #'(lambda (b) (if (consp (car b)) (car b) b)) 를 한번씩 거친다.
; (car b)가 리스트면 그대로 보내고 (car b)가 리스트가 아니라면 b를 놓는다.(리스트 형태를 그대로 유지한다. 해당 심볼은 nil이 될듯 바로 (b nil)로 바인딩된다.
; 왜냐하면 (b nil) == (cons b (cons nil))이니까...
; 특이한 점은 리스트가 여기서 다 바인딩 되는 것이 아니다. 바깥에 바인딩 된 녀석들만 바인딩이 되는데
; 중요한 것은 여기서 바인딩 안에 들어갈 새로운 seq의 gensym 값도 바인딩이 된다. 
; 그리고 그녀석이 다음 재귀에서 만들어질 녀석들이 사용할 seq값이다.
; 다 돌면 body를 푼다.
(defun dbind-ex (binds body)
  (if (null binds)
      `(progn ,@body)
      `(let ,(mapcar #'(lambda (b)
                         (if (consp (car b))
                             (car b)
                             b))
                     binds)
        ,(dbind-ex (mapcan #'(lambda (b)
                               (if (consp (car b))
                                   (cdr b)))
                           binds)
                   body))))


;(print
; (macroexpand '(dbind (a b c) (list 1 2 3) (list a b c))))
;(LET ((#:G3210 (LIST 1 2 3)))
; (LET ((A (ELT #:G3210 0)) (B (ELT #:G3210 1)) (C (ELT #:G3210 2)))
;  (PROGN (LIST A B C))))
; #:G3210 binding에 연결.
; (ELT가 뭐지... ELT로 해당 앨리먼트의 0번째 1번째 2번째를 가져오는 것 같다.)
; 그 후 (PROGN에 바디를 바인딩한다.

;(print
; (destruc '(a b c) (list 1 2 3) #'atom)
; )
; ((A (ELT (1 2 3) 0)) (B (ELT (1 2 3) 1)) (C (ELT (1 2 3) 2))) 


; 리스트 안에 리스트가 있는 경우
'(print
 (macroexpand '(dbind (a (b c)) (list 1 (list 2 3)) #'atom)))
;(LET ((#:G3210 (LIST 1 (LIST 2 3))))
; (LET ((A (ELT #:G3210 0)) (#:G3211 (ELT #:G3210 1)))
;  (LET ((B (ELT #:G3211 0)) (C (ELT #:G3211 1))) (PROGN #'ATOM)))) 
;이걸보면 알 수 있는 것이. 바인딩 되는 심볼이 깊이가 있으면 그 갯수만큼 (gensym)이 있게 된다.
;그 이유는 바인딩할 seq(list 1 (list 2 3))이 달라지기 때문이다.
;위 코드에서 두번째 코드가 신기한데. 일단 새롭게 바인딩할 값 또한 리스트의 리스트로 들어가야 하는 상황인 것이다.
;(#:G3211 (ELT #:G3210 1))
;이 코드인데 (#:G3211 (elt (list 1 (list 2 3)) 1)이 되는 것이다.
;그러면 (#:G3211 (list 2 3))이 되면서 리스트안으로 들어가게 된다.
;

'(print
 (destruc '(a (b c)) (list 1 (list 2 3)) #'atom))
; ((A (ELT (1 (2 3)) 0))
; ((#:G3210 (ELT (1 (2 3)) 1)) (B (ELT #:G3210 0)) (C (ELT #:G3210 1)))) 

(print
 (dbind-ex '((A (ELT (1 (2 3)) 0))
              ((#:G3210 (ELT (1 (2 3)) 1)) (B (ELT #:G3210 0)) (C (ELT #:G3210 1)))) 
            (list 1 (list 2 3))))
;(LET ((A (ELT (1 (2 3)) 0)) (#:G3210 (ELT (1 (2 3)) 1)))
; (LET ((B (ELT #:G3210 0)) (C (ELT #:G3210 1))) (PROGN 1 (2 3)))) 

(print
 (dbind-ex '((A (ELT (1 2 3) 0)) (B (ELT (1 2 3) 1)) (C (ELT (1 2 3) 2))) '(list a b c)))
;(LET ((A (ELT (1 2 3) 0)) (B (ELT (1 2 3) 1)) (C (ELT (1 2 3) 2)))
;  (PROGN LIST A B C)) 

;subseq가 뭔지도 몰랐다.
'(print (subseq (list 1 2 3) 0)) ; (1 2 3)
'(print (subseq (list 1 2 3) 1)) ; (2 3)
'(print (subseq (list 1 2 3) 2)) ; (3)


2019년 9월 2일 월요일

[on lisp] 감상평

on lisp를 3분의2를 읽고 이제 잠시 다른 책을 읽을까 한다. Land Of Lisp의 번역본을 읽다 만 적이 있는데, on lisp를 보다보니 읽을 수 있을 것만 같다.

on lisp는 대단한 책이다. 한명이서 이렇게 대단한 글을 쓸 수 있는지 놀랍다. 아주 간결하고, 무섭다. 뒤로가면 갈 수록, 꽤나 어렵기는 하지만 쉽게 쓰는 것은 그가 할 일이 아닌 것 같다.

일단 이제 on lisp에 대한 내용은 적지 않고 눈팅만 할까 한다.

사실 이 책으로 커먼리습의 개념정도만 훑고 매크로에 대해서 clojure라는 언어랑 어떻게 다른지 그정도만 알려고 했으나, 끌리듯 매크로의 내용까지 읽게 되었다.
매크로의 내용은 꽤나 흥미로웠다.
많은 부분 배울 점이 많았다. 영어의 장벽으로인해 읽는 속도가 느려짐에 따라 내 머리가 터지는 느낌을 받았다.

그러다가 커먼리습의 매크로부분을 한 반정도 읽고선 더 이상은 보류를 해야 겠다고 생각했다.
정리를 할 시간이 필요하다. 다른 책을 보면서 쉬고, 다시 읽던가 해야할 것 같다.

커먼리습은 clojure랑 궁극적으로 다른 것이 있는데 그것이 mutability이다. 커먼리습은 값을 마음대로 바꿀 수 있다. clojure에서는 그것이 불가능하다.
커먼리습은 그렇기 때문에 매크로에서 더욱더 많은 일을 할 수 있으며, 더욱더 많은 문제를 일으킨다.
그 많은 내용들을 읽다가, 나는 대충 훑어 넘길 수 밖에 없었는데, 일단 나에게는 너무 먼 이야기처럼 느껴졌다.
피부에 느껴질때 읽어도 늦지 않겠다고 생각했다.

그리고 함수형을 기반으로 프로그래밍을 한다면 해당 문제는 많이 사라질 것이라고 생각했다.
할 수 있는 일이 줄어든다고, 못하는 것은 아니니까.
화공은 화선지 안에서 자유를 느낀다고 했던가.
조그만한 네모 종이 위에서 큰 자유를 느낀다고 한다. 그 자유는 아마 자신을 그 하얀색 안으로 단절시키고, 바깥 세상에 더럽혀지지 않은 순수한 무언가를 만들 수 있는 것 때문 아닐까 싶다.

다음 책은 Land Of Lisp를 읽어야 겠다.
그리고 한 번 Let Over Lambda라는 책을 읽어볼까 한다.
얼마나 어려운 책인지.

얼마나 무서운 책인지 알고 싶다.
더 나아가 매크로가 어디까지 갈 수 있는지 알고 싶다.

멋진 책이다. lisp의 개론서로는 딱이다. 매크로의 내용은 인내심을 가지고 보면 멋진 내용이 기다릴 것이다.
하지만 이 책을 읽는 것은 시간이 필요한 일이다.
느긋하게 보았으면 좋겠다.

나도 좀 더 느긋하게 볼 생각이다.

2019년 9월 1일 일요일

[on lisp] 11 Classic Macros


11.1 Creating Context
Context here has two senses. One sort of context is a lexical environment.
The let special form creates a new lexical environment; the expressions in the body of a let will be evaluated in an environment which may contain new variables.

If x is set to a at the toplevel, then
(let ((x 'b)) (list x))
with nonetheless return (b), because the call to list will be made in an environment containing a new x, whose value is b.

; Fig 11.1 : Macro implementations of let.
; let 만들어보자.
; 1 binds는 바인딩 되는 리스트 body는 이제 표현식
; 2 mapcar로 binds에서 (car x)로 키 값만 가져온다.
; 2.1 cons가 아니면 x로 
; 2.2 그리고 그것은 매개변수에 들어간다. (lambda 매개변수 ..)
; 2.3 body를 ,@body로 리스트를 벗긴다.

; 3 이제 람다 안에 binds 안에 값을 넣는다.
; 3.1 mapcar로 binds에서 (cdr x)로 밸류만 가져온다.
; 3.2 cons가 아니면 nil로 바인딩
(defmacro our-let (binds &body body)
  `((lambda ,(mapcar #'(lambda (x)
                         (if (consp x) (car x) x))
      binds)
    ,@body)
  ,@(mapcar #'(lambda (x)
                 (if (consp x) (cadr x) nil))
      binds)))
보면 알겟지만, let은 매크로 안에서 lambda에서 만든다.
(our-let ((x 1) (y 2))
  (+ x y))
;; 아래로 확장
((lambda (x y) (+ x y)) 1 2)

그림 11.2는 lexical 환경을 만들어서 바인딩하는 3개의 새로운 매크로를 제공한다.
; Fig 11.2 : Macros which bind variables.
(defmacro when-bind ((var expr) &body body)
  `(let ((,var ,expr))
     (when ,var
    ,@body)))

(defmacro when-bind* (binds &body body)
  (if (null binds)
      `(progn ,@body)
   `(let (,(car binds))
      (if ,(caar binds)
       (when-bind* ,(cdr binds) ,@body)))))

(defmacro with-gensyms (syms &body body)
  `(let ,(mapcar #'(lambda (s)
                     `(,s (gensym)))
     syms)
  ,@body)) 
when-bind는 섹션 7.5에서 리스트 파라미터 구조분해로 쓰임. 그러므로 이 매크로는 94페이지에서 이미 설명이 됨
(when-bind (input (get-user-input))
  (process input))
;; expand
(let ((input (get-user-input)))
  (when input
    (process input)))
더 나아가서 when-bind*는 symbol expression(바인딩 될 심볼과 함수)쌍의 리스트를 받는다. -- let의 첫번째 매개변수와 같다.
If any expression returns nil, the whole when-bind* expression returns nil.
이중에 하나라도 nil을 뱉으면 when-bind* 모두 nil임. nil 아니면 body가 평가되게 된다. 그리고 각 symbol들은 let*으로 묶인다.
(when-bind* ((x (find-if #'consp '(a (1 2) b)))
             (y (find-if #'oddp x)))
  (+ y 10))
11
마지막으로 with-gensyms 매크로다. 매크로를 작성할 때 쓰이는 녀석.
많은 매크로들이 gensyms를 생성하는 것으로 시작한다.
종종 이게 많을 때가 있다. page 115에 with-redraw는 5개나 gensym로 만든다
(defmacro with-redraw ((var objs &body body)
  (let ((gob (gensym))
        (x0 (gensym)) (y0 (gensym))
        (x1 (gensym)) (y1 (gensym)))
  ...))
;; 이제 이렇게
(defmacro with-redraw ((var objs) &body body)
  (with-gensyms (gob x0 y0 x1 y1)
    ...))

만약 변수들을 바인딩하려할 때, 조건절에 따라서 다르게 평가를 한다면?
let안에 조건절을 사용하자.

하지만 반대로는? 조건에 따라서 바인딩이 달라져야 한다면
(defmacro condlet (clauses &body body)
  (let ((bodfn (gensym))
        (vars (mapcar #'(lambda (v) (cons v (gensym)))
                      (remove-duplicates
                        (mapcar #'car
                                (mappend #'cdr caluses))))))
    '(labels ((,bodfn ,(mapcar #'car vars)
                 ,@body))
       (cond ,@(mapcar #'(lambda (cl) 
                           (condlet-clause vars cl bodfn))
                       clauses)))))

(defun condlet-clause (vars cl bodfn)
  `(,(car cl) (let ,(mapcar #'cdr vars)
                (let ,(condlet-binds vars cl)
                  (,bodfn ,@(mapcar #'cdr vars))))))

(defun condlet-binds (vars cl)
  (mapcar #'(lambda (bindform)
              (if (consp bindform)
                  (cons (cdr (assoc (car bindform) vars))
                        (cdr bindform))))
          (cdr cl)))
그림 11.3이 그런경우를 보여주는 것이다.
(condlet (((= 1 2) (x (princ 'a)) (y (princ 'b)))
          ((= 1 1) (y (princ 'c)) (x (princ 'd)))
          (t       (x (princ 'e)) (z (princ 'f))))
  (list x y z))
CD
(D C NIL)

11.2 The with- Macro
with-형태의 매크로를 만들어서 컨텍스트를 생성한다.
(with-open-file (s "dump" :direction :output)
  (princ 99 s))
이러면 저절로 "dump"파일을 닫히고 99가 써있게 될 것이다.
; pure macro
(defmacro with-db (db &body body)
  (let ((temp (gensym))
    `(let ((,temp *db*))
       (unwind-protect
         (progn
           (setq *db* ,db)
           (lock *db)
           ,@body)
         (progn
           (release *db*)
           (setq *db* ,temp))))))
; with function
(defmacro with-db (db &body body)
  (let ((gbod (gensym)))
    `(let ((,gbod #'(lambda () ,@body)))
       (declare (dynamic-extent ,gbod))
       (with-db-fn *db* ,db ,good))))

(defun with-db-fn (old-db new-db body)
  (unwind-protect
    (progn
      (setq *db* new-db)
      (lock *db*)
      (funcall body))
    (progn
      (release *db*)
      (setq *db* old-db))))
다른 매크로도 보자.
; Fig 11.5: Macros for conditional evaluation.
(defmacro if3 (test t-case nil-case ?-case)
  `(case ,test
     ((nil) ,nil-case)
     (?     ,?-case)
     (t     ,t-case)))

(defmacro nif (expr pos zero neg)
  (let ((g (gensym)))
    `(let ((,g ,expr))
       (cond ((plusp ,g) ,pos)
             ((zerop ,g) ,zero)
             (t ,neg)))))

11.3 Conditional Evaluation 조건평가
;; fig 11.6 : Macros for conditional evaluation.
(defmacro in (obj &rest choices)
  (let ((insym (gensym)))
    `(let ((,insyn ,obj))
       (or ,@(mapcar #'(lambda (c) `(eql ,insym ,c))
                     choices)))))

(defmacro inq (obj &rest args)
  `(in ,obj ,@(mapcar #'(lambda (a) '' ,a)
                      args)))

(defmacro in-if (fn &rest choices)
  (let ((fnsym (gensym)))
    `(let ((,fnsym ,fn))
       (or ,@(mapcar #'(lambda (c)
                         `(funcall ,fnsym ,c))
                     choices)))))

(defmacro >case (expr &rest clauses)
  (let ((g (gensy)))
    `(let ((,g ,expr))
       (cond ,@(mapcar #'(lambda (cl) (>casex g cl))
                       clauses)))))

(defun >casex (g cl)
  (let ((key (car cl)) (rest (cdr cl)))
     (cond ((consp key) `((in ,g ,@key) ,@rest))
           ((inq key t otherwise) `(t ,@rest))
           (t (error "bad >case clause")))))

11.4 Iteration 반복
; 11.7 : Simple iteration macros.
(defmacro while (test &body body)
  `(do ()
       ((not ,test))
      ,@body))

(defmacro till (test &body body)
  `(do ()
       (,test)
     ,@body))

(defmacro for ((var start stop) &body body)
  (let ((gstop (gensym)))
    `(do ((,var ,start (1+ ,var))
          (,gstop ,stop))
         ((> ,var ,gstop))
       ,@body)))

[on lisp] 9. 매크로 캡처링문제

; 9.1 variable capture

;wrong
(defmacro for ((var start stop) &body body)
    `(do ((,var ,start (1+ ,var))
          (limit ,stop))
         ((> ,var limit))
         ,@body))

; seems work fine
(for (x 1 5)
     (princ x))
;error 이렇게 limit을 넣으면 문제가 된다.
;(for (limit 1 5)
;     (princ limit))
;why
; (> limit limit) 이름 충돌 여기도 변수충돌이 일어난다.
;(do ((limit 1 (1+ limit))
;     (limit 5))
;    ((> limit limit))
;    (princ limit))

; 9.2 Free Symbol Capture
; 덜 빈번하게, 매크로 정의 자체에서 매크로가 확장 될 때 environment에 실수로 바인딩을 하는 기호를 포함할 수도 있다. (환경변수가 어쩌다 걸려버린것)
; 어떤 프로그램을 소개한다, warning을 출력하는 대신, 나중에 확인하려고 리스트에 add하기를 원한다고 해보자.
; 한 개발자가 매크로 gripe를 작성한다. 경고를 글로벌 리스트(w)에 넣는 다.
(defvar w nil)

(defmacro gripe (warning)
    `(progn (setq w (nconc w (list ,warning)))
         nil))

; 그런 다음 다른 사람이 sample-ratio라는 함수를 만들고 그 안에 gripe를 넣었다.
; 이 함수는 매개변수 v,w의 길이의 비율을 확인한다. 거기서 비율이 2보다 작으면 해당 내용을 로그리스트(w)에 추가하고
; nil을 리턴한다. 아래 코드를 보다.

(defun sample-ratio (v w)
  (let ((vn (length v)) (wn (length w)))
    (if (or (< vn 2) (< wn 2))
        (gripe "sample < 2")
        (/ vn wn))))
; [sample-ratio]가 w=(b)로 호출된다면, w의 매개변수는 2개 이하 이기 때문에 워닝을 할 것이다.
; 하지만 gripe이 확장되는 순간, sample-ratio 안에 정의된 것처럼 보이게 된다.

(defun sample-ratio (v w)
  (let ((vn (length v)) (wn (length w)))
     (if (or (< vn 2) (< wn 2))
         (progn (setq w (nconc w (list "sample < 2")))
                 nil)
         (/ vn wn))))
; 문제는 gripe매크로가 sample-ratio안으로 들어가면서 w가 글로벌 변수 w를 쓰는 것이 아니라.
; sample-ratio안에 있는 로컬변수에 바인딩 되는 것이다. 원래는 free variable이 되서 글로벌로 범위가 커지는 것인데
; 이제는 free-variable이 아니라 로컬변수처럼 된 것이다.
; warning은 이제 글로벌 리스트에 저장되는 것이 아니라, 로컬변수에 들어간다.
; 이제는 warning출력값만 잃은 것이 아니라. 매개변수로 들어온 W=(b)값 마저 잃게 된다.
(print (let ((lst '(b)))
  (sample-ratio nil lst)
   lst))
; (B "sample < 2") lst가 이렇게 바뀐 것이다.
(print w)
; nil : w에는 아무것도 들어가지 않았음


; 9.3 When Capture Occurs (언제 캡처가 일어나나)
; 변수 캡처는 미묘한 문제이며 캡처 가능한 기호가 프로그램에서 장난을 일으킬 수있는 모든 방법을 예상하려면 약간의 경험이 필요하다.
; 다행스럽게도, 캡처로 인해 프로그램이 잘못 될 수 있는 방법에 대해 생각 할 필요없이 매크로 정의에서 캡처 가능한 기호를 감지하고 제거 할 수 있다.
; 캡처 가능한 변수를 정의하는 규칙은 먼저 정의해야하는 일부 하위 개념에 따라 다릅니다.

; Free : 기호 [s]는 해당 표현식에서 변수로 사용될 때 표현식에서 사용 가능하지만 표현식은 이에 대한 바인딩을 작성하지 않습니다.
; s가 변수로 들어오면 사용하지만, 표현식 안에서 바인딩을 생성 하지는 말라는 말인가. 아래를 보자
; (let ((x y) (z 10))
;   (list w z x))
; w,x,z모두 list표현식에서는 free variable이다, 바인딩이 없는 것이다.
; 하지만 let으로 둘러싸면 x,z에 대한 바인딩을 적용한다. 좋아 let 안 전체를 보면 y,w는 free variable이 되는 것이다.
; 또 알아야 하는 것은
; (let ((x x))
;   x)
; 여기서 (x x)가 있는데 두번째 x만 free다 이 녀석은 x를 위해 바인딩 된 새로운 스코프에서 온 녀석이 아니다.

; Skeleton : 매크로 확장의 골격은 전체 표현식이며, 매크로 호출에서 매개변수 부분을 뺀 것이다.
(defmacro foo (x y)
  `(/ (+ ,x 1) ,y))
; 그리고 호출해보자
(print
  (macroexpand-1
    '(foo (- 5 2) 6)))
; (/ (+ (- 5 2) 1) 6) 
; 여기서 스켈레톤은 확장된 전체 표현식 중에서 파라미터 x y를 뺀 녀석이다.
; (/ (+         1)  )
; 이 두 가지 개념을 정의하면, 캡쳐 가능한 기호를 탐지하기 위한 간결한 규칙을 진술하는 것이 가능하다.

; Capturable : 심볼은 몇몇 매크로 확장에서 캡처될 수 있다.
; (a) free variable이 매크로 확장의 스켈레톤 안에 있던가
; (b) 매개변수로 들어온 변수가 (바인딩하거나 평가하는) 스켈레톤의 부분에 바인딩 되는 경우 (덮어씌어지는 경우)
(defmacro cap1 ()
  '(+ x 1))
; 여기서 x는 capturable이다. 왜냐하면 free variable이 스켈레톤 안에 있다. gripe에서도 본 버그이다.
(defmacro cap2 (var)
  `(let ((x ...)
         (,var ...))
     ...))
; 여기서 x는 capturable이다. x가 매개변수로 들어오는 x도 바인딩 해버린다. (9.1 for에서 확인했다.)
; 반면에 아래 두 매크로를 보자.
(defmacro cap3 (var)
  `(let ((x ...))
     (let ((,var ...))
       ...)))

(defmacro cap4 (var)
  `(let ((,var ...))
     (let ((x ...))
       ...)))
; 모두 x가 capturable이다. 하지만, x가 바인딩 된 곳 안에 문맥(context)가 없고, 매개변수로 들어온 녀석이 없다면, cap3/cap4 모두 사용할 수 있다.
(defmacro safe1 (var)
  `(progn (let ((x 1))
            (print x))
          (let ((,var 1))
            (print ,var))))
; 여기서 x는 capturable이 아니다. 
; 잘보면! let를 쓰고 그 안에서 x만 쓴다. 매개변수 var를 쓰고 있지 않다. 게다가 환경변수 같이 밖에 있는 문맥(context)를 끌어들이지 않았다.
; 이처럼 모든 스켈레톤 안에 있는 bound variables가 위험하지는 않다.
; 하지만 만약 매개변수가 스켈레톤에 의해 설정된 바인딩 내에서 평가되는 경우는,
(defmacro cap5 (&body body)
  `(let ((x ...)
     ,@body))
; 그러면 바인딩된 변수는 캡처링 될 위험이 있다. cap5에서 x는 캡처위험이 있다.
(defmacro safe2 (expr)
  `(let ((x ,expr))
     (cons x 1)))
; x는 캡처위험이 없다, 왜냐하면 expr에 전달된 매개변수를 평가할 때, 새로 바인딩 된 x는 보이지 않기 때문이다.
; 또한 우리가 걱정해야 하는 것은 the binding of skeletal variables(스켈레톤 변수의 바인딩)이다. 아래 매크로를 보자.
(defmacro safe3 (var &body body)
  `(let ((,var ...))
     ,@body))
; 어떤 심볼도 의도치 않게 갭처될 위험이 없음.
; 이제 캡처 가능한 기호를 식별하는 새로운 규칙에 비추어 'for'의 원래 정의를 살펴보자.
(defmacro for ((var start stop) &body body) ;; wrong
  `(do ((,var ,start (1+ ,var))
        (limit ,stop))
       ((> ,var limit))
     ,@body))
; 이제 위에 정의는 두 가지 방식으로 취약하는 것을 알게 되었다.
; 1. limit 이 for의 첫번째 매개변수로 들어올 수 있다.
(for (limit 1 5)
  (print c limit))
; 2. 그런데 limit이 loop의 body에 들어가는 것도 위험하다.
(let ((limit 0))
  (for (x 1 10)
    (incf limit x))
  limit)
; 여기서 for문은 끝나지 않을 것이다.
; 이 섹션에서 보여준 룰들로 전부 잡을 수 있는 것은 아니다. 
; capture문제는 보는 것에 따라 모호하게 정의된다. 예를 즐다
(let ((x 1)) (list x))
; 우리는 (list x)를 평가할 때 그게 오류라고 하지는 않는다. x가 새로운 변수에 들어간다고 문제라고 생각하지는 않을 것이다.
; 캡처링 문제를 알아내는 룰도 부정확하다.
; 이런 캡처링 룰을 패스하는 매크로를 만들 수 있지만, 그럼에도 의도치 않은 캡처링 문제를 직면할 수 있다.
; 아래 예를 보자.
(defmacro pathological (&body body)
  (let* ((syms (remove-if (complement #'symbolp)
                          (flatten body)))
         (var (nth (random (length syms))
                   syms)))
    `(let ((,var 99))
        ,@body)))
; 매크로가 호출 될 때, body 안에 표현식은 progn처럼 평가될 것이다.
; but one random variable within the body may have a different value.
; 하지만 body안에 있는 random 변수는 다른 값을 가질 것이ㅏ.

; 9.4 Avoiding Capture with Better Names (더 나은 이름으로 캡처링을 피한다?)
; 첫번째 두 섹션에서 캡처링을 두 가지 타입으로 나눴다.
; argument capture, 매크로 스켈레톤에 매개변수가 들어가는 거
; free symbol capture, free symbol이 매크로 확장 안에 있어서 예기치 못한 곳에 캡처링
; 이름을 *warning* 이렇게 글로벌로 넣으면서 캡처링이 되지 않도록 하자는 것 같다.
; 좋은 생각 같지는 않다.

; Avaoiding Capture by Prior Evaluation
; 때로 매개변수 캡처링은 매크로확장에서 만들어진 바인딩 밖에서 위험한 매개변수를 확장하면서 간단히 해결된다.
; Fig 9.1 Avoiding capture with let.
;; vulnerable to capture
(defmacro before (x y seq)
  `(let ((seq ,seq))
     (< (position ,x seq)
        (position ,y seq))))
; currect version
(defmacro before (x y seq)
  `(let ((xval ,x) (yval ,y) (seq ,seq))
     (< (position xval seq)
        (position yval seq))))

; 9.2 Avoiding capture with a closure.
; vulnerable to capture
(defmacro for ((var start stop) &body body)
  `(do ((,var ,start (1+ ,var))
        (limit ,stop))
       ((> ,var limit))
     ,@body))
; correct version
(defmacro for ((var start stop) &body body)
  `(do ((b #'(lambda (,var) ,@body))
        (count ,start (1+ count))
        (limit ,stop))
       ((> count limit))
     (funcall b count)))
; 이렇게 사용할 녀석들을 전부 let으로 다시 바인딩해서 사용하는 걸 원하나보다.


; 9.6 Avoiding Capture with Gensyms * (gensyms이용하기)
; gensyms는 같은 값이 없는 것을 약속하는데
; 이 건 각 패키지에서 모든 심볼의 이름을 추적한다.
; 이 심볼들은 패키지 안에 interned된다고 말한다.
; gensym을 호출하면 uninterned unique한 심볼을 리턴한다.
; 그러므로 
(eq (gensym) ...)
; 이렇게 테스트를 해보려고 하면 절대 일어날 수 없는 일이 될 것이다.
(gensym) ; #G:47 뭐 이런식으로 보여준다. 이름을 딱히 중요하지 않다. 

;9.3 Avoiding capture with gensym.
(defmacro for ((var start stop) &body body)
  `(do ((,var ,start (1+ ,var))
        (limit ,stop))
       ((> ,var limit))
     ,@body))
; A correct version
(defmacro for ((var start stop) &body body)
  (let ((gstop (gensym)))
    `(do ((,var ,start (1+ var))
          (,gstop ,stop))
         ((> ,var ,gstop))
       ,@body)))
; 그림 9.3을 보면 gensyms를 이용한 정의가 있다.
; 이제 limit 혹은 stop 뭐 이런걸로 심볼이 충돌나지는 않을 것이다.

; 9.7 Avoiding Capture with Packages. (패키지로 캡처링 피하기)
; 어느정도는 매크로를 패키지 않에 넣는 것으로 캡처링을 피하는 것이 가능하다.
; 만약 매크로들의 패캐지를 따로 만들어서(macros라고 하자) 그 안에 for매크로를 만든다면 초반에 만든 매크로 마저 쓸 수 있게 된다.
(defmacro for ((var start stop) &body body)
  `(do ((,var ,start (1+ ,var))
        (limit ,stop))
       ((> ,var limit))
     ,@body))
; 같은 패키지는 아니고 다른 패키지에서 사용한다고 해보자.
; mycode라는 패키지에서 macros패키지를 사용한다고 해보자.
; limit이라는 이름으로 첫번째 매개변수를 사용한다 하더라도!
; mycode:limit이 되고 macros::limit이 되어 충돌이 되지 않을 것이다.
; 문제는 뭐냐 1. 패키지는 이런식으로 쓰라고 만들어 진 것이 아니다.
; 2. 같은 패티지에서는 문제가 생긴다.

; 9.8 Capture in Other Name-Spaces (다른 네임스페이스에서의 캡처)
; 지금까지는 variable capture이지만, 커먼리습에서는 name-space가 문제될 수 있다.
; 함수도 또한 로컬에서 바인딩 될 수 있으며, 함수 바인딩은 변수바인딩과 같이 문제를 일으키기 쉽다.
(defun fn (x) (+ x 1))
(defmacro mac (x) `(fn ,x))
(mac 10) ;; 11
(labels ((fn (y) (- y 1)))
  (mac 10) ;; 9
; 위치에 따라 fn이 다르게 캡처링 되면서 리턴값이 달라진다.