2019년 9월 11일 수요일

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 방식이라던가 그런 설정값이다.

댓글 없음:

댓글 쓰기