2019년 8월 31일 토요일

[on lisp] 8. When to Use Macro

최근에 갑상선 암으로 수술을 하였다. 술도 잘 안먹고, 담배도 피지 않는 내가 왜 암이라는 것에 걸리는 걸까. 라는 생각을 하게 된다.
아직도 목 부근이 아프다. 힘도 안난다. 운동을 강하게 할 수 는 없을 것 같다. 평생 약을 먹어야 하는 것도 걱정이다. 아니 좀 짜증이라고 해야 하나...
하지만, 뭐 어쩔 수 없다고 생각하게 되었다. 내가 인자약이라 생각하고, 음식을 하나하나 제한하기로 했다.

그리고...
오랜만에 on lisp를 다시 공부하고 있다.
커먼리습을 잘 모르기에 읽는데 꽤나 힘들다. 잘 몰라도 내 시야를 넓혀주는 느낌을 준다.

이런 공부 정말 시간이 오래걸리고 내가 영어능력이 후달려서 제대로 읽고 있는지도 잘 모르지만,
꽤나 재미있다.

이제 겨우 8장을 넘겼는데, 언제 마지막까지 갈까 한숨이 나온다.
끈질기게 계속 나아가야 겠다.

; 8 When to Use Macro
; 8.1 When Nothing Else Will Do
; 일반적으로 비슷한 코드를 프로그램에서 찾는 다면
; 서브루틴을 만들고 비슷한 일련의 코드들을 없애고 서브루틴을 호출하는 것으로 바꿀 것이다.
; 이 원칙을 리스프 프로그램에 적용하면, 
; 우리는 이 "서브루틴"이 함수여야 할지 매크로여야 할 지 고민해야 한다.

; 어떤 경우에는 매크로밖에 할 수 없어서, 매크로로 결정해야 하는 경우가 있다.
; 1+ 함수 같은 경우 함수/매크로 둘다 만들 수 있다.
(defun myfun-1+ (x) (+ 1 x))
(defmacro mymacro-1 (x) `(+ 1 ,x))

; 하지만 while같ㅇ느 경우 매크로밖에 못한다.
(defmacro while (test &body body)
  `(do ()
       ((not ,test))
     ,@body))
; while은 body 표현식을 쪼개서(splice) do의 body부분에 넣는다.
; 게다가 평가는 test변수가 오로지 nil일 경우다.
; 함수는 이런거 못한다 함수는 호출되기 전에 매개변수가 모두 평가 호출된다.
; 이걸 알자 매크로는 함수가 못하는 4가지를 할 수 있다.
; 1. Transformation(변형)
; 커먼리습에서 setf 매크로는 평가 전에 인수를 구분(평가전에 매개변수를 다룬다)하는 매크로 클래스 중 하나다.
; 내장 액세스 함수는 종종 그 반대(역)를 가진다. 이 녀석의 목적은 내장 엑세스 함수가 조회하는 것을 설정한다(set 반대로 한다는 말)
; 예를 들어 car는 첫번째를 가져오는데 replaca는 첫번째를 설정한다.
; setf 를 함께 이용하면 이런 access function(car, cdr)를 이용하여 변수를 set할 수 있다.
; (setf (car x) 'a) 이렇게 말이다. 이 값은 (progn (rplaca x 'a) 'a)로 확장될 수 있다.
; 이 트릭을 수행하려면 setf는 첫 번째 인수를 살펴 봐야한다. 그래야 뭘로 바꿀지 알 수 있다.
; 위의 경우 replaca가 필요하다는 것을 알기 위해 setf는 첫 번째 인수가 car로 시작하는 표현식임을 확인할 수 있어야함.
; 따라서 setf 및 인수를 변환하는 다른 연산자는 매크로로 작성해야합니다.
; 그러니까 매개변수에 들어오는 표현식이 평가되지 전에 확인해야 하는 것이 있는 것이다.
; 나의 예측으로는 setf의 첫번째 매개변수의 표현식이 car이라면 replaca로 cdr이면 rplacd로 변환(transformation)할 것으로 보인다.
; 그러므로 이럴 때는 함수를 쓸 수 없다. 왜냐하면 매개변수로 들어가면 이미 평가가 되고 나서 함수에 들어오기 때문에다.
; 표현식을 볼 수 없다.

; 2. Binding(바인딩, 변수바인딩인듯)
; Lexical variables must appear directly in the source code.
; 렉시컬 변수는 소스 코드에 직접 나타나야합니다.
; 예를 들어 setq에 대한 첫 번째 인수는 평가되지 않으므로 setq에 빌드 된 것은 호출하는 함수가 아니라 setq로 확장되는 매크로 여야합니다.
; 비슷한 연산자 let을 보자, 이녀석에게 오는 매개변수들은 lambda 표현식의 매개변수로 바뀐다.(매크로를 풀면)
; do 매크로는 letdmfh ghkrwkdehlsek. 
; 어떤 연산자든 매개변수의 lexical binding을 손보려면 매크로여야 한다.
; 이름이 평가되기 전에 lambda로 감싼 후 매개변수로 보내서 lexcial binding을 만들어야 한다.

; 3. Conditional evaluation.(조건에 따른 평가)
; 함수의 경우 모든 매개변수는 평가된다.
; when을 예로 들면 특정 조건에 맞을 때만 body가 평가된다.
; 이런 유연성은 매크로에서만 가능하다.

; 4. Multiple evaluation.(여러번 평가, for문 처럼)
; 함수에는 매개변수는 모두 평가될 뿐만 아니라 정확히 한.번. 만 평가된다.
; do 매크로 처럼 여러번 실행되게 하려면 매크로가 필요하다.

; 다음은 inline expansion of macros의 장점이다.
; 이제 알려줄 세 가지 장점 중 두 가지는 lexical context를 다루는 점 때문에 나타난다.
; 5. Using the calling environment. 
; 매크로를 매크로를 호출하는 컨텍스트에 바인딩된 변수를 포함한 확장 코드를 생성할 수 있다.
(defmacro foo (x)
  `(+ ,x y))
; depends on the binding of y where foo is called.
; 이건 기본적으로 나쁜 스타일이다.
; 하지만, 환경값을 가져오기 위해 아주 드물게 필요한 기술이다.

; 6. Wrapping a new environment.
; A macro can also cause its arguments to be evaluated in a new lexical environment.
; 매크로는 또한 새로운 lexical environment에서 인수가 평가되도록 할 수 있습니다.
; let을 말하는 것! 페이지 144에 let 매크로 구현예제가 있다.
; (let ((y 2)) (+ x y)), 에서 y는 새로운 변수를 가리킨다.

; 7. Saving function calls. 
; 매크로 확장의 인라인 삽입의 세 번째 결과는 컴파일 된 코드에서 매크로 호출과 관련된 오버 헤드가 없다는 것입니다.
; 런타임에서, 매크로 호출은 그 확장코드로 치환된 상태를 실행하는 것이다.
; (함수를 인라인으로 만드는 것과 비슷한 원리)

; 5,6번은 의도되지 않았을 경우(제대로 알지 못한 경우) , variable capture문제를 겪게 된다.
; 이 문제는 macro를 가장 위협하는 존재다. 이 문제는 챕터9에서 심도있게 다룰 것.
; Instead of seven ways of using macros, it might be better to say that there are six and a half.
; In an ideal world, all Common Lisp compilers would obey inline declarations,
; and saving function calls would be a task for inline functions, not macros.
; An ideal world is left as an exercise to the reader.

; 8.2 Macro or Function?
; 8.1의 내용은 쉬웠다. 대부분 연산자는 매개변수가 바로 평가되면 안된다. 매크로만 할 수 있는 건 매크로로 만들면 되는 것이었다.
; 둘다 만들어질 수 있는 경우, 우린 어떻게 선택해야 하는가? 어떤 장단점이 있는지 제대로 알아야 한다.
(defun avg (&rest args)
  (/ (apply #'+ args) (length args)))
(defmacro avg (&rest args)
  `(/ (+ ,@args) ,(length args)))
; 여기서 함수버전은 각 avg호출에 length가 불필요하게 실행됨.
; 컴파일 타임에 우리는 인수의 값을 알지 못할 수도 있지만 얼마나 많은지 알기 때문에 'length'에 대한 호출도 가능합니다.
; 뭔 말이야, 몇개인지 어떻게 안다는 거지...
; 뭐 여튼 컴파일 타임에 인수의 갯수를 알 수있으니 length호출을 매번 할 필요는 없다는 말인가?


; 여기서 함수를 쓸지 매크로를 쓸지 선택을 도와줄 요소들이 있다.
; 1. Computation at compile-time(컴파일타임에 계산이 된다.)
; 매크로 호출에서 계산은 두번 일어난다 : 매크로가 확장 될 때와 확장이 평가 될 때.
; 리습에서 모든 매크로 확장은 프로그램이 컴파일 될 때 일어나며, 모든 
; 컴파일 타임에 계산이 완료된 비트들 중 1비트도 프로그램이 실행될 때 속도를 늦추지 않을 것이다.
; If an operator could be written to do some of its work in the macroexpansion stage, it will be more efficient to make it a macro
; 만약 한 연산자가 매크로 확장 단계에서 일분 작업을 미리 수행할 수 있게 할 수 있다면, 매크로로 만들어 더 효율적이게 만들 수 있는것이다.
; 왜냐하면 어떤 아무리 똑똑한 컴파일러가 와도 스스로 할 수 없다. 함수는 런타임에 수행되어야 하기 때문이다.
; Chapter 13에서 avg 매크로가 어떻게 확장하는지 보여준다.

; 2. Integration with Lisp(Lisp와의 통합)
; 때때로 함수 대신 매크로를 사용하면 프로그램이 Lisp와보다 밀접하게 통합 될 수 있다.
; 특정 문제를 해결하기 위해 프로그램을 작성하는 대신! 매크로를 사용하여 Lisp가 이미 해결 방법을 알고있는 문제로 변환 할 수 있다.
; 이 방법은, 가능하면, 일반적으로 프로그램을 더 작고 효율적으로 만든다.
; 더 작아지고, 리스프가 우리 일부 작업을 수행해주기 때문에, 더 효율적으로, 프로덕션 리스프 시스템은 일반적으로 유저 프로그램보다 fat sweated(땀투성이가 된다?)하기때문에
; (아마 리스프 시스템이 유저가 만드는 프로그램보다 더 커지기 때문에? 더 일을 많이 하기 때문에, 유저는 프로그램을 만들 때 효율적으로 만든다는 건가)
; 이런 장점은 대부분 embedded languages에서 나타난다. 19챕터에서 보여줄 것임.

; 3. Saving function calls.(함수호출을 줄인다)
; 매크로 호출은 호출이 되는 그곳에 바로 확장된 내용이 코드 안으로 들어간다(치환된다).
; 따라서 자주 사용하는 코드를 매크로로 작성하면 사용될 때마다 함수 호출을 절약할 수 있다.
; Lisp의 초기 방언에서 리습 프로그래머는 이 매크로 속성을 활용하여 런타임에 함수 호출을 절약했다.
; 일반적인 Lisp에서 이 작업은 인라인으로 선언 된 함수에 의해 수행됩니다. (inline으로 함수를 만든다는 듯, 이름이 없음)
; 함수를 [인라인]으로 선언하면 매크로와 마찬가지로 호출 코드로 바로 컴파일되도록 요청합니다.
; 그러나 여기에는 이론과 실제 사이에 차이가 있다.
; CLTL2 (p.229)는 "컴파일러는 이 선언을 무시해도됩니다"라고 말하고 일부 Common Lisp 컴파일러는 그렇게합니다.
; 이러한 컴파일러를 사용해야하는 경우 매크로를 사용하여 함수 호출을 절약하는 것이 여전히 합리적 일 수 있습니다.(인라인을 무시하는 컴파일러)

; 어떤 경우에는 효율성과 Lisp와의 통합이 매크로 사용에 대한 강력한 논거가 될 수 있다.
; 19 장의 쿼리 컴파일러에서 보면, 런타임에 계산되어야할 녀석들이 컴파일 타임에서 계산이 되니까(또 그 양이 많아서), 
; 전체 프로그램을 하나의 거대한 매크로로 전환하는 것을 정당화 할 수 있다.
; 이렇게 전체가 거대한 매크로가 되는 전환은 속도를 빠르게 하기도 하지만 프로그램을 리스프에 더 가깝게 하기도 한다. (domain specific language가 되는듯)
; 이렇게 바뀐 후에는 쿼리 내에서 Lisp 표현식 (예 : 산술 표현식)을 사용하는 것이 더 쉽다.

; THE CONS 단점
; 4. Functions are data, while macros are more like instructions to the compiler.
; 함수는 데이터지만 매크로는 컴파일러에게 보내는 명령어들과 비슷하다.
; 함수는 리턴하거나 매개변수를 받거나 자료구조 안에 저장될 수 있지만, 매크로는 아무것도 못한다.
; 람다표현식 안에다가 매크로를 넣어서 이런 것들을 할 수 있도록 트릭을 쓸 수도 있겠다.
; 예를들어 apply,funcall같은 것을 매크로에 적용해야 한다면
(funcall #'(lambda (x y) (avg x y)) 1 3)
2
; 하지만 편하지는 않다. 그리고 이게 항상 되는 것은 아니다. 
; avg같이 단순한 매크로라 하더라도 말이다. 매크로는 &rest 매개변수를 가진다.
; 이 안에 다양한 인수를 전달할 방법이 없습니다. 그렇다 람다에서 받는다하더라도 그걸 풀어서 넣는 방법이 있을까.
; 그 매개변수 자체를 데이터로 조작해야 하는데 그건 매크로에서 할 수 있을 것이다.

; 5. Clarity of source code (소스코드의 명확성, 코드가 더러워짐)
; 매크로는 함수로 만드는 것보다 읽기가 힘들다. 그러므로 매크로를 쓴다는 것은 눈에 띌만큼 낫다는 것을 알고 있을 때다.

; 6. Clarity at runtime. (런타임에서의 명확성, 런타임에서 디버깅이 힘들다)
; 매크로는 종종(항상) 함수보다 디버깅이 힘들다.
; 매크로는 확장시 사라지므로 런타임에서는 보이는 녀석으로는 무엇을 하는 녀석인지 제대로 설명되지 못한다.
; 전혀 작동하지 않으면 trace는 매크로 호출 자체가 아니라 매크로의 확장 기능 호출을 보여줍니다.

; 7. Recursion (재귀가 문제)
; 매크로에서 재귀를 사용하는 것은 함수랑 다르다. (힘들다)
; 매크로에서 확장된 함수은 재귀적일 수 있지만 확장하는 것 자채는 쉽지 않다.
; 섹션 10.4는 매크로의 재귀를 다룰 것이다.

; 매크로 사용 결정은 위 고려사항들을 균형있게 다뤄야 한다.
; 오로지 경험만이 무엇이 우선으로 되는지 알게 된다.
; 그러나 다음 장에 나오는 매크로의 예는 매크로가 유용한 대부분의 상황을 다룬다.
; 미래에 만들어진 매크로가 아래 주어진 매크로와 유사하다면, 작성하는 것이 안전 할 것이다.
; 마지막으로 런타임시의 명확성(단점 6번)은 거의 문제가 되지 않다.
; 많은 매크로를 사용하는 코드의 디버깅은 예상만큼 어렵지 않다.
; 매크로 정의의 길이가 수백 줄인 경우 런타임에 확장을 디버그하는 것이 불쾌 할 수 있습니다.
; 그러나 유틸리티는 최소한 작고 신뢰할 수있는 계층으로 작성되는 경향이 있다. 
; 일반적으로 그 정의는 15줄 미만이다. 이런 매크로에서는 보는게 어렵진 않을 거다.

; 8.3 Applications for Macros (매크로 응용)
; 어떤 종류의 응용 프로그램에서 사용할 수 있을까?
; 가장 가까운 것(잘 맞는 것)은 syntactic transformation이다.
; 19-24 장의 모든 syntactic transformation(구문변환)으로 설명할 수 있는 전체 프로그램을 제시한다.
; 이런 것들은 실제로 모두 매크로다.
; 매크로앱은 작은 형태의 범용 매크로(while 매크로)와 큰 특정목적에 맞는 매크로 사이의 연속이다.
; 한쪽 끝에는 [유틸리티]가 있으며, 모든 Lisp에 내장 된 것과 유사하다.
; 그것들은 일반적으로 작고 일반적이며 독립적으로 작성된다.
; 그러나 특정 클래스의 프로그램에 대한 유틸리티도 작성할 수 있다. 이제 그래픽 프로그램에 사용할 매크로 모음이 있으면 그래픽을 위한 프로그래밍 언어처럼 보이기 시작할 것이다.
; 이 연속의 맨 끝에서(유틸리티의 반대편), 매크로를 사용하면 Lisp와 다른 언어(매크로로 만들어진 새로운 언어)로 전체 프로그램을 작성할 수 있습니다.
; 이러한 방식으로 사용되는 매크로는 embedded languages를 구현한다고합니다.

; 유틸리티 매크로는 bottom-up 스타일의 첫번째 자손이다.
; 심지어 프로그램이 너무 작아서 계층을 쌓기어렵다해도, 가장 낮은 계층에 Lisp자체를 추가하면 도움이 될 것이다.
; 유틸리티 nil!을 보자, 이녀석의 매개변수들을 모두 nil로 값을 넣는다. 이런 건 매크로로만 가능하다.
(defmacro nil! (x)
  `(setf ,x nil))

; nil!을 보자, 어떤 사람은 이걸 보고 이 매크로는 아무것도 하지 않는 녀석이라고 말 할 것이다. 단지 타이핑을 절약할 뿐이라는 것이라고.
; 맞다. 하지만 모든 매크로가 하는 일은 타이핑 절약이다.
; 컴파일러의 일은 기계어로 타이핑을 절약하는 일이다.
; 유틸리티의 가치를 과소평가 하면 안된다, 왜냐하면 이것들의 효과는 누적된다.
; 간단한 매크로의 몇몇 계층이 우아한 프로그램과 이해할 수 없는 프로그램의 차이를 만든다.
; 대부분 유틸리티는 이미 당신이 구현한 패턴들에서 나온다.
; 당신의 코드에서 패턴이 보이면, 유틸리티로 바꾸는 것을 고려하자.
; 패턴이 있는 일은 컴퓨터가 잘하는 거다. 왜 프로그램이 당신을 위해 해줄 수 있는데, 왜 그 프로그램에서 주어진 것들로만 개발하여 당신을 괴롭히는가.
; 어떤 프로그램을 작성할 때, 같은 일반적인 형태의 여러 다른 장소에서 [do] 루프를 사용한다고 가정하자.
(do ()
    ((not ))
     )
; 이런 패턴을 반복적으로 봤다면, 이 패턴은 종종 제대로 딱 맞는 이름이 있을 것이다.
; 여기서 그 이름은 while이라고 부른다. 이걸 새로운 유틸리티로 제공하고 싶다면, 매크로를 써야 한다.
; 왜냐하면! 우리는 조건적으로 평가를 해야 하고, 반복적으로 평가해야 한다.
; while의 구현은 page 91에 있다.
(defmacro while (test &body body)
  `(do ()
       ((not ,test))
       ,@body))
이제 이렇게 쓰면 된다.
(while 
  )
; 코드가 더 짧아지고 더 명확하게 선언된다.
; 인수를 변환하는 기능은 인터페이스 작성에서 매크로가 유용하게 되는 이유다.
; 적절한 매크로를 사용하면 길고 복잡한 표현이 필요한 경우! 짧고 간단한 표현으로 바꿀 수 있다.

; 그래픽 인터페이스를 예로들면 비록 그래픽 인터페이스에서 앤드유저를 위한 매크로 작성의 필요성은 감소하지만
; 프로그래머는 그 어느 때 보다 많이 사용한다.
; 가장 일반적인 예는 defun이다. 이녀석 때문에 함수를 바인딩하는 것이 표면적으로 Pascal/C 같은 언어와 유사하게 만들 수 있게 된 것이다.
; Chapter 2에서 보여줬지만 아래 두 표현식은 같은 효과를 낸다.
(defun foo (x) (* x 2))

(setf (symbol-function 'foo)
      #'(lambda (x) (* x 2)))

defun매크로는 후자의 표현식으로 바꿔주는 것으로 구현된다(대략적으로)
(defmacro our-defun (name params &body body)
  `(progn
     (setf (symbol-function ',name)
           #'(lambda ,params (block ,name ,@body)))
     'name))

; while,nil!같은 매크로는 general-purpose 유틸리티로 볼 수 있다. 이것들은 모든 리습 프로그램에서 사용할 수 있다.
; 하지만! 특정 도메인은 그들만의 유틸리티를 가질 수 있다.
; base lisp(리스프언어)만이 우리가 확장해야할 계층이라고 생각하면 안된다.
; CAD 프로그램을 예로들자, 가장 좋은 결과는 때때로 두개의 계층으로 작성하는 것이다.
; 1. a language(좀 더 마일드하게 표현하면 툴킷), CAD program만을 위한 언어를 만드는 것이다.
; 2. 그리고 그 계층 위에 특정 앱을 구현하는 것이다.

; lisp는 다른 언어가 당연스럽게 여기는 구분을 흐릿하게 만든다.
; 다른 언어에서는, 컴파일타임/런타임, 프로그램/데이터, 언어/프로그램 같은 것들 사이에 개념적 차이가 있다.
; Lisp에서는 오직 대화로 만들어지는 규칙(conversational conventions)에선 존재한다.(너희들이 정하는 거다? 뭐 이런 뜻이려나)
; 뚜렷한 구분선이 없다. 예를들어보자, 언어와 프로그램의 구분선이 리습에 확실하게 있는지...
; 당신은 문제가 있는 곳바다 선을 그릴 수 있다. 따라서 기본 코드 레이어(기본 코드 계층에서 만든 매크로 포함)를 툴킷이라 할지, 언어라 할지 그건 용어의 차이에 불과하다.
; 이걸 언어라고 생각할 때의 장점은,  Lisp를 사용하는 것 같이, 유틸리티를 사용하여 언어를 확장할 수 있다는 것이다.

; 대화식 2D 그리기 프로그램을 만든다 가정하자. 
; 간단히 하기 위해, 프로그램에 처리되는 유일한 객체는 원점와 벡터로 표시되는 선분(line segments)이라 가정하자.
; 이런 프로그램이 해야 할 일은 하나의 개체 그룹을 미끄러지듯이 움직이게 하는 것이다.(slide)
; 이것에 그림 8.1의 move-objs가 하는 일이다. 효율적으로, 우리는 각 연산이 완료되엇을 때 스크린의 전부를 redraw하고 싶지 않다.
; 우리는 변경된 부분만 redraw하고 싶다. 하여, bounds함수를 두번(이동전,후) 호출하여 (min x, min y, max x, max y 사각형)객체 그룹의 경계를 리턴한다.
; "move-objs"의 작동 부분은 "bounds"에 대한 두 호출 사이에 끼워져 이동 전후에 경계 사각형을 찾은 다음 영향을받는 전체 영역을 다시 그린다.
; "scale-objs"는 객체의 그룹의 사이즈를 바꾼다. 역시 비슷한 패턴을 가진다. 이건 뭐 회전, 반전, 뭐 이런거에 전부 적용될 듯하다.
; 여기서 그림 8.1의 구현을 보자.

; Fig 8.1 Original move and scale.
(defun move-objs (objs dx dy)
  (multiple-value-bind (x0 y0 x1 y1) (bounds objs)
    (dolist (o objs)
      (incf (obj-x o) dx)
      (incf (obj-y o) dy))
    (multiple-value-bind (xa ya xb yb) (bounds objs)
      (redraw (min x0 xa) (min y0 ya)
              (max x1 xb) (max y1 yb)))))

(defun scale-objs (objs factor)
  (multiple-value-bind (x0 y0 x1 y1) (bounds objs)
    (dolist (o objs)
      (setf (obj-dx o) (* (obj-dx o) factor)
            (obj-dy o) (* (obj-dy o) factor)))
    (multiple-value-bind (xa ya xb yb) (bounds objs)
      (redraw (min x0 xa) (min y0 ya)
              (max x1 xb) (max y1 yb)))))


; 매크로를 이용하면 우리는 이 패턴의 코드를 추상화할 수 있다.
; 그림 8.2 with-redraw는 이 패턴의 스켈레톤을 제공한다.
; 이렇게 하니 함수들이 4줄로 바뀌게 된다. 각각!
; Fig 8.2 Move and scale filleted
(defmacro with-redraw ((var objs) &body body)
  (let ((gob (gensym))
        (x0 (gensym)) (y0 (gensym))
        (x1 (gensym)) (y1 (gensym)))
    `(let ((,gob ,objs))
       (multiple-value-bind (,x0 ,y0 ,x1 ,y1) (bounds ,gob)
         (dolist (,var ,gob) ,@body)
         (multiple-value-bind (xa ya xb yb) (bounds ,gob)
           (redraw (min ,x0 xa) (min ,y0 ya)
                   (max ,x1 xb) (max ,y1 yb)))))))

(defun move-objs (objs dx dy)
  (with-redraw (o objs)
    (incf (obj-x o) (dx)
    (incf (obj-y o) (dy)))

(defun scale-objs (objs factor)
  (with-redraw (o objs)
    (setf (obj-dx o) (* (obj-dx o) factor)
          (obj-dy o) (* (obj-dy o) factor))))

; 우리는 이 with-redraw를 대화식 그리기 프로그램을 위한 언어 안에 있는 구성물로 볼 수 있다.(언어의 파트가 된 것?)
; 우리가 더 많은 매크로를 개발함에 따라, 그것들은 실제로 이름뿐만 아니라 프로그래밍 언어와 유사하게 될 것이며, 
; 우리의 응용 프로그램 자체는 특정 요구에 맞게 정의 된 언어로 작성된 프로그램에서 기대할 수있는 우아함을 보이기 시작할 것.

; 매크로의 다른 주요용도는 embedded language를 만드는 것이다.
; 리습은 프로그래밍 언어를 작성하기 아주 좋은 언어이다.
; 왜냐하면 리습 프로그램은 리스트로 표현될 수 있으며, 리습은 그렇게 표현된(리스트로 표현된?!) 빌트인 파서(read)와 컴파일러(compile)가 있다.
; 변환을 수행하는 코드를 컴파일하여 내장 언어 컴파일러를 암시적으로 가질 수 있다. (25 페이지).
; embedded language는 리스프 위에 작성되지 않은 언어이다. 하여 신텍스는 리스프와 새로운 언어에 특정한 구조들의 혼합이다.
; 임베디드 언어를 구현하는 나이브한 방법은 리습에서 인터프리터를 작성하는 것이다.
; 가능한 경우, 더 나은 방법은 변환(transformation)을 통해 언어를 구현하는 것이다.
; 인터프리터가 해당 표현식을 평가하기 위해 실행했을 리습 코드로 변환하라.
; 이 개념에서 매크로가 들어오는 것이다.
; 매크로의 역할은 한 표현식의 형태를 다른 형태로 변환하는 것이다. 하여 임배디드언어(내장언어)를 만들 때 자연적으로 선택하게 되는 기술이다.

; 일반적으로 변환으로 임베디드 언어를 더 많이 구현할 수록 좋다.
; 첫번째, 일을 적게한다. 
; 예를 들어, 새로운 언어에 산술이 있다면 숫자 수량을 표현하고 조작하는 모든 복잡성에 직면 할 필요는 없습니다.
; 만약 리습의 산술능력이 당신의 목적에 충분한 만큼 괜찮다면, 산술 표현식을 동등한 Lisp 표현식으로 변환하고 나머지는 Lisp에 남겨 둘 수 있습니다.

; 변환을 사용하면 일반적으로 내장 언어도 더 빨라진다.
; 인터프리터는 속도와 관련하여 고유한 단점이 있다.
; 예를 들어 루프 내에서 코드가 발생하면 인터프리터는 각 반복에 대해 작업을 수행해야하는 경우가 종종 있습니다. 컴파일 된 코드에서는 한 번만 수행 할 수 있는데...
; 자체 인터프리터가 있는 임베디드 언어는 느려진다. 인터프리터 자체가 컴파일된다해도 그렇다.
; 그러나 새로운 언어의 표현식이 리습으로 변환되면, 결과코드는 리습컴파일러에 의해 컴파일 될 수 있다.
; 이런게 구현된 언어는 런타임에 인터프리테이션의 오버헤드를 겪지 않아도 된다.
; 언어에 맞는 진정한 컴파일러 작성은 매크로가 최상의 성능을 제공한다.
; 실제로, 새로운 언어를 변형시키는 매크로는 이것을 위한 컴파일러라고 볼 수 있다.
; 대부분의 작업을 수행하기 위해 기존 lisp 컴파일러에 의존하는 것이다.
; 여기서 임베디드 언어에 대한 예제는 다루지 않을 것이며 19-25에서 다룰 것임

; 챕터 19는 인터프리팅과 임베디드 언어 변환과의 차이를 다룰 것이다.
; 그리고 두 방식에 대한 구현을 각각 제시한다.

; 어떤 커먼리습을 책은 매크로의 스코프가 아주 제한적이라고 한다.
; 그리고 CLTL1에 정의된 연산자의 사실을 증거로 인용하는데, 10% 미만의 매크로만이 구현되어 있다고 한다.
; 이 말은 집이 벽돌로 되어 있다고 가구도 벽돌로 되어있어야 한다는 논리다.
; 커먼리습 프로그램 안에 매크로 비율은 전적으로 해야 할 일이 달려 있습니다.
; 일부 프로그램에는 매크로를 아예 가지지 않는다.
; 일부 프로그램은 모두 매크로 일 수 있다.

댓글 없음:

댓글 쓰기