2019년 9월 11일 수요일

[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 함수를 쓸 수 있다.
하지만, 만약 요구사항이 이것보다 조금만 복잡해지면, 매크로를 써야 한

댓글 없음:

댓글 쓰기