2019년 8월 29일 목요일

[on lisp] 7 매크로 (여러가지 매크로 정의들)

; 7. Macros

(defmacro nil! (var)
  `(setq ,var nil))
  
(defmacro nif (expr pos zero neg)
  `(case (truncate (signum ,expr))
     (1 ,pos)
     (0 ,zero)
     (-1 ,neg)))
     
(defmacro while (test &body body)
  `(do ()
       ((not ,test))
     ,@body))
(print "macroexpand '(while (able) (laugh))))")
(pprint (macroexpand '(while (able) (laugh))))
(print "macroexpand-1 '(while (able) (laugh))))")
(pprint (macroexpand-1 '(while (able) (laugh))))

(defmacro mac (expr)
  `(pprint (macroexpand-1 ',expr)))

; 7.5 Destructuring in Parameter Lists
; destructuring-bind macro (new in CLTL2) takes a pattern,
; an argument evaluating to a list, and a body of expressions, 
; and evaluates the expressions with the parameters in the pattern bound
; to the corresponding elements of the list
(print "(destructuring-bind (x (y) . z) '(a (b) c d))")
(print (destructuring-bind (x (y) . z) '(a (b) c d)
  (list x y z)))

(defmacro our-dolist ((var list &optional result) &body body)
  `(progn
     (mapc #'(lambda (,var) ,@body)
           ,list)
     (let ((,var nil))
       ,result)))

(defmacro when-bind ((var expr) &body body)
  `(let ((,var ,expr))
     (when ,var
       ,@body)))
; called as follows:
; (when-bind (input (get-user-input))
;   (process input))
; instead of:
; (let ((input (get-user-input)))
;   (when input
;     (process input)))

;7.6 A model of Maros
; Since defmacro is itself a macro, we can give it the same treatment.
; This definition uses several techniques which haven't been covered yet,
; so some readers may want to refer to it later.
; Fig 7.6 A sketch of defmacro
(defmacro our-expander (name) `(get ,name 'expander))

(defmacro our-defmacro (name params &body body)
  (let ((g (gensym)))
    `(progn
       (setf (our-expander ',name)
             #'(lambda (,g)
                  (block ,name
                    (destructuring-bind ,params (cdr ,g)
                      ,@body))))
       ',name)))
       
(defmacro our-defmacro (name params &body body)
  (let ((g (gensym)))
    `(progn
       (setf (our-expander ',name)
             #'(lambda (,g)
                 (block ,name
                   (destructuring-bind ,params (cdr ,g)
                     ,@body))))
        ',name)))
        
(defun our-macroexpand-1 (expr)
  (if (and (consp expr) (our-expander (car expr)))
      (funcall (our-expander (car expr)) expr)
      expr))
;; 매크로 기능들을 직접 만듬.
; 7.7 Macros as Programs
; A macro is a function which transforms one sort of expression into another.
; 지금까지 쉬운 매크로였다. 하지만 do 처럼 어려운 예라면?
; 그림7.7에 do가 어떻게 확장되는지 보여준다.
; Fig 7.7
(print "Fig 7.7 do")
(do ((w 3)
     (x 1 (1+ x))
     (y 2 (1+ y))
     (z))
    ((> x 10) (princ z) y)
  (princ x)
  (princ y))
(print "Fig 7.7 do expand")
(prog ((w 3) (x 1) (y 2) (z nil))
  foo ;goto statement
    (if (> x 10)
        (return (progn (princ z) y)))
    (princ x)
    (princ y)
    (psetq x (1+ x) y (1+ y))
  (go foo))

; 이제 이걸로 do를 만들어보자.
; Fig 7.8 Implementing do.
(defmacro our-do (bindforms (test &rest result) &body body)
  (let ((label (gensym)))
    `(prog ,(make-initforms bindforms)
       ,label
       (if ,test
           (return (progn ,@result)))
       ,@body
       (psetq ,@(make-stepforms bindforms))
       (go ,label))))
; 위 예제에서 ((w 3) (x 1) (y 2) (z nil)) 이거 만드는 곳
(defun make-initforms (bindforms)
  (mapcar #'(lambda (b)
              (if (consp b)
                  (list (car b) (cadr b))
                  (list b nil)))
          bindforms))
; mapcan는 mapcar와 비슷하지만 nconc를 사용한다.(mapcar는 list)
; 그러므로 값이 nil이면 리턴값에는 들어가지 않는다.
(defun make-stepforms (bindforms)
  (mapcan #'(lambda (b)
              (if (and (consp b) (third b))
                  (list (car b) (third b))
                  nil))
          bindforms))
; 이건 실제구현과는 다르다고 한다.
; 아래 2개로 쪼갠 make-initforms, make-stepforms는 나중에 defmacro 안으로 들어갈 것이다.

; Fig 7.8 Macro Style
; 좋은 스타일은 매크로에서는 다르게 의미할 수 있다.
; Style matters when code is either read by people or evaludated by Lisp.
; With macros, both of these activites take place under slightly unusual circumstances.
; 매크로 정의에서 두 가지 종류의 코드가 있다.
; 1. expander code, the code used by the macro to generate its expansion
; 2. expansion code, which appears in the expansion itself.
; expander code can favor clarity over efficiency, and expansion code can favor efficiency over clarity.
; expander코드는 확장을 생성하는 코드. expansion code는 확장된 코드 그 자체
; 예를보자. (and a b c)는 (if a (if b c))와 같다.
; and를 그림 7.9에서 만들어보자. 
; our-and는 속도측면에서보는 보면 별로다. 이 확장코드는 재귀적이며, 각 재귀는 같은 리스트의 cdr 길이를 잰다.
; 만약 이 코드가 런타임에 평가된다면, our-andb가 좀 더 나을 것이다.
; 하지만 our-and가 더 좋지는 않더라도 좋은 정도라고 할 수 있다.
; length를 재귀적으로 계속 호출해서 비효율적일 수는 있으나, 코드가 좀 더 깨끗하게 느껴진다.
; Fig 7.8 Two macros equivalent to and.
(defmacro our-and (&rest args)
  (case (length args)
    (0 t)
    (1 (car args))
    (t `(if ,(car args)
            (our-and ,@(car args))))))

(defmacro our-andb (&rest args)
  (if (null args)
      t
      (labels ((expander (rest)
                 (if (cdr rest)
                     `(if ,(cdr rest)
                          ,(expander (cdr rest)))
                     (car rest))))
        (expander args))))
; expander code에서
; (a) only affects the speed of compilation
; (b) doesn't affect it very much -- meaning the clarity should nearly always come first
; expansion code, it's just the opposite
; Clarity matters less for macro expansions because they are rarely looked at.
; expansion코드에는 클린코드가 낫다. 확장되서 만들어진 코드는 더러워도 괜찮다.

;7.9 Dependence on Macros
;1. Define macros before functions (or macros) which call them.
;2. When a macro is redefined, also recompile all the functions (or macros)
;   which call it -- directly or via other macros.

; 7.10 Macros from Functions
; 함수에서 매크로로 변경하는 방법을 배울 것. 문제는 정말 필요한가이다.
; 몇가지 정당화할 이유가 있다.
; When you begin writing macros, it sometimes helps to think as if you were writing a function
; 매크로를 만들려고 할 때, 함수를 만드는 것처럼 생각하면 도움이 크게 된다.
; 다른 하나는 매크로/함수 살펴보면, 서로의 차이를 제대로 볼 수 있다는 점.
; 마지막으로 리스프 개발자는 가끔 실제로 함수를 매크로로 바꾸고 싶어한다.

;(defun second (x) (cadr x))
(defmacro second2 (x) `(cadr ,x))
; (defun noisy-second (x)
;   (princ "Someone is taking a cadr!")
;   (cadr x))
(defmacro noisy-second (x)
  `(progn
     (princ "Someone is taking a cadr!")
     (cadr ,x)))
; (defun sum (&rest args)
;   (apply #'+ args))
(defmacro sum (&rest args)
  `(apply #'+ (list ,@args)))  
(defmacro sum2 (&rest args)
  `(+ ,@args))

;(defun foo (x y z)
;  (list x (let ((x y))
;            (list x z))))
(defmacro foo (x y z)
  `(list ,x (let ((x ,y))
              (list x ,z))))

; 7.11 Symbol Macros
; CLTL2는 symbol-macro라는 새로운 매크로를 제시함.
; 일반 매크로는 함수 호출처럼 호출한다. 
; symbol-macro의 "호출"은 심볼같다(?)
; Symbol-macro는 오로지 로컬에서만 정의된다.
(symbol-macrolet ((hi (progn (print "Howdy")
                             1)))
  (+ hi 2))
; "Howdy"
; 3
; 나중에 더 알아볼 것임. 15쯤

댓글 없음:

댓글 쓰기