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% 미만의 매크로만이 구현되어 있다고 한다.
; 이 말은 집이 벽돌로 되어 있다고 가구도 벽돌로 되어있어야 한다는 논리다.
; 커먼리습 프로그램 안에 매크로 비율은 전적으로 해야 할 일이 달려 있습니다.
; 일부 프로그램에는 매크로를 아예 가지지 않는다.
; 일부 프로그램은 모두 매크로 일 수 있다.

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쯤

2019년 8월 25일 일요일

[on lisp] 6.2 Compiling Network (클로저이용 중요)

6.1에서 사용한 전통적인 방식은 어느 프로그래밍 언어로도 가능하다.
우리는 하지만 더 나은 방법을 제시한다.

Fig 6.5를 보자.
해당 자료구조를 위한 노드를 가지고, 순회하기 위한 분리된 코드를 가지는 대신! 우리는 클로저로 노드를 표현한다.
run-node같은 것은 이제 필요없다. 모두 노드 안에 들어가게 된다.
새로운 코드를 보기 전에 이전 방식을 떠올려보자.
순회를 시작하려면 어디부터 시작할 곳을 꺼내서 실행하면 된다.
(funcall (gethash 'people *nodes*)) 
Is the person a man? 
>>
이제 클로저로 노드를 표현하게 되면, 우리는 20개의 질문 네트워크 전부를 코드 안에 넣을 수 있게 된다.
지금 현재 코드는 런타임에 노드 함수를 이름에 따라서 찾아(look-up)야 한다.
하지만 만약 이 네트워크가 그때그때 재정의 되지 않는다고 한다면! 우리는 좀 더 개선할 수 있게 된다.
* 이제 해시테이블을 거치지 않고 직.접. 목적지로 가게 하는 노드함수를 만드는 것.
(defvar *nodes* nil)

(defun defnode (&rest args) 
  (push args *nodes*) args)

(defun compile-net (root) 
  (let ((node (assoc root *nodes*))) 
    (if (null node) 
        nil 
        (let ((conts (second node)) 
              (yes (third node)) 
              (no (fourth node))) 
          (if yes 
            (let ((yes-fn (compile-net yes)) 
                  (no-fn (compile-net no))) 
              #'(lambda () (format t "~A~%>> " conts) 
                               (funcall (if (eq (read) ’yes) 
                                             yes-fn 
                                             no-fn)))) 
            #'(lambda () conts))))))
Figure 6.6: Compilation with static references.

When the original call to compile-net returns, it will yield a function representing the portion of the network we asked to have compiled.

그림6.6이 새로운 버전이다. 이제 *nodes*는 계속 사용되는 hast-table이 아닌 일회용의 리스트이다.
모든 노드는 defnode로 먼저 정의된다, 하지만 지금 클로저가 생기는 것은 아니다.
노드가 모두 정의된 다음에 compile-net함수를 실행하여 모든 네트워크를 한번에 컴파일 한다.

이 함수는 트리의 리프까지 재귀적으로 작동하며 백업(돌아올라오는) 도중에 각 단계에서 두 개의 서브트리 각각에 대한 노드/함수를 반환합니다.
1. 하여 각 노드는 두개의 노드를 아예 다룰 수 있도록 자체를 가지고 있게 된다. (목적지 이름만 가지고 있는 것이아니라.)
원래 compile-net에 대한 호출이 리턴하면 이 값은 네트워크 자체를 표현하는 함수가 만들어진 것이다.(모든 네트워크 연결을 클로저로 완성한)
> (setq n (compile-net ’people)) 
# 
> (funcall n) 
Is the person a man? 
>>
compile-net은 두가지 의미로 컴파일 되는 것이다.
1. 일반적인 의미로는, 추상적인 네트워크 표현을 코드로 변환한다.
2. 더 나아가서, compile-net 자체가 컴파일 되면, 이녀석은 컴파일된 함수를 리턴할 것이다.(See page 25)
그러니까 내용물을 컴파일 하냐, compile-net 코드 자체냐

네트워크를 컴파일한 이후에는, defnode로 만든 리스트는 필요없다.
nil로 덮어서 가비지 컬렉터가 묶여있는 메모리를 풀 수 있다.

6.3 Looking forward


네트워크를 포함하는 많은 프로그램들은 이렇게 노드들을 클로저에 담는 방식으로 컴파일하여 구현할 수 있다.
클로저는 데이터 객체들이다, 그리고 자료구조가 사물을 표현하는 것또한 할 수 있다.
이렇게 하기 위해선 약간 관습적인 사고(훈련)이 필요하지만, 그 보상은 더 빠르고 우아한 프로그램이다.

Macros help substantially when we use closures as a representation.
매크로는 클로저를 표현하는데 실질적인 도움이 된다.
“To represent with closures” is another way of saying “to compile,” and since macros do their work at compile-time, they are a natural vehicle for this technique.
"클로저로 표현하기"는 "컴파일하기"라는 작업의 또 다른 방식이며, 매크로는 일 자체를 컴파일 타임에 하기 때문에, 이 기술을 위한 자연스러운 수단이다.
After macros have been introduced, Chapters 23 and 24 will present much larger programs based on the strategy used here.
매크로에 대해 소개를 한뒤, 챕터 23, 24에서 이제 소개될 전략에 대해서 더 큰 프로그램을 재현할 것이다.

[on lisp] 6.1 function as representation (Network)

Generally, data structures are used to represent.
자료구조는 표현을 하기 위해 사용된다.
An array could represent a geometric transformation;a tree could represent a hierarchy of command; a graph could represent a rail network.
배열은 기하학적 변화를, 트리는 명령 계층을, 그래프는 철도 네트워크를
In Lisp we can sometimes use closures as a representation.
리스프에서 우리는 때때로 무언가를 표현할 때, 클로저를 사용하기도 한다.
Within a closure, variable bindings can store information, and can also play the role that pointers play in constructing complex data structures.
클로저 안에서, 변수 바인딩은 정보를 저장할 수 있다, 그리고 복잡한 자료구조의 경우 포인터로 가지고 놀 수 있다.
By making a group of closures which share bindings, or can refer to one another, we can create hybrid objects which combine the advantages of data structures and programs.
바인딩을 공유, 서로를 참조 할 수 있다. 즉, 우리는 이걸 이용해서 프로그램과 자료구조의 장점을 합친 hybrid object를 만들 수 있다.

Beneath the surface, shared bindings are pointers.
깊게 들어가면, 바인딩 공유는 포인터이다.
Closures just bring us the convenience of dealing with them at a higher level of abstraction.
클로저는 그저 높은 추상화를 이용하여 이 포인터를 편하게 다룰 수 있도록 한 것이다.
By using closures to represent something we would otherwise represent with static data structures, we can often expect substantial improvements in elegance and efficiency.
정적데이터를 클로저로 표현하면, 우아함과 효율성을 향상 시킬 수 있다.

Network

Closures have three useful properties: they are active, they have local state, and we can make multiple instances of them.
클로저에 3가지 속성을 가진다: active(살아있고)하고 local state를 가지고, 여러개의 instance를 만드는 것이 가능하다.
Where could we use multiple copies of active objects with local state? In applications involving networks, among others.
이렇게 살아있는 객체의 여러 복사본을 어디에서 사용할까? 특히 네트워크 관련 응용프로그램에서.
In many cases we can represent nodes in a networkas closures.
대부분의 경우 네트워크를 클로저로 나타낼 수 있다.
As well as having its own local state, a closure can refer to another closure.
자기자신의 로컬 상태만 가질 수 있는 것이 아니라, 클로저는 다른 클로저도 참조할 수 있다.
Thus a closure representing a node in a network can know of several other nodes (closures) to which it must send its output.
하여 네트워크에 노드를 표현하는 클로저는 출력을 보내야 하는 여러 노드(클로저)를 알 수 있다.
This means that we may be able to translate some networks straight into code.
이 말은 네트워크를 코드로 바로 표현할 수 있는 것이다.
; Fig 6.1 : Session of twenty questions  
> (run-node ’people) 
Is the person a man? 
>> yes 
Is he living? 
>> no 
Was he American? 
>> yes 
Is he on a coin? 
>> yes 
Is the coin a penny? 
>> yes 
LINCOLN
In this section and the next we will look at two ways to traverse a network.
지금 이 섹션과 다음 섹션을 통해 네트워크를 순회하는 2가지 방법을 알아보자.
First we will follow the traditional approach, with nodes defined as structures, and separate code to traverse the network.
첫번째는 전통적인 방식을 따른다, 노드는 자료구조를 정의하고, 분리된 코드로 네트워크를 순회한다.
Then in the next section we’ll show how to build the same program from a single abstraction.
그리고 다음 섹션에서는 추상화된 한 곳에서 위 전통적 방식과 동한 프로그램을 작성하는 방법을 보자.

As an example, we will use about the simplest application possible: one of those programs that play twenty questions.
예를들어, 우리는 아주 간단한 앱을 사용할 것이다: 그중하나가 20개의 질의를 하는 프로그램이다.
Our network will be a binary tree.
우리의 네트워크는 이진트리가 될 거다.
Each non-leaf node will contain a yes/no question, and depending on the answer to the question, the traversal will continue down the left or right subtree.
리프가 아닌 각 노드는 yes/no 질문을 가지고 있다, 질문에 대한 답에 따라서, 트리의 순회는 왼쪽/오른쪽 서브트리로 내려간다.
Leaf nodes will contain return values. When the traversal reaches a leaf node, its value will be returned as the value of the traversal. A session with this program might look as in Figure 6.1.
리프 노는 리턴 값을 진다. 리프노드에 닿으면 그 값이 리턴된다. Fig 6.1를 보면 세션을 가진 프로그램을 보여준다.

The traditional way to begin would be to define some sort of data structure to represent nodes.
전통적인 방식은 노드를 나타내는 일종의 자료구조를 정의한다.
A node is going to have to know several things: whether it is a leaf; if so, which value to return, and if not, which question to ask; and where to go depending on the answer.
노드는 몇 가지 요소를 가져야 한다. 1. 리프인지 아닌지, 리프는 값을 리턴한다, 2. 아니면 리턴하지 않고 어떤 질문을 하는지. 3. 답에 따라 어디를 가나.
A sufficient data structure is defined in Figure 6.2. It is designed for minimal size.
Fig 6.2에 충분한 자료구조가 정의 되어있다. 최소한의 크기로 설계되어 있다.
; Fig 6.2 : Representation and definition of nodes.
(defstruct node contents yes no)

(defvar *nodes* (make-hash-table))

(defun defnode (name conts &optional yes no)
  (setf (gethash name *nodes*)
        (make-node :contents conts :yes yes :no no)))
The contents field will contain either a question or a return value.
내용 필드에는 질문 또는 리턴값이 있다.
If the node is not a leaf, the yes and no fields will tell where to go depending on the answer to the question;
노드가 리프가 아닌경우, yes/no 필드는 질문에 대한 답변에 따라 어디로 가야할지 알려준다.
if the node is a leaf, we will know it because these fields are empty.
리프라면 이 필드들은 비어있기 때문에, 리프 임을 알 수 있다.
The global *nodes* will be a hash-table in which nodes are indexed by name.
글로벌 *nodes* 변수는 이름별로 인덱싱 되는 해시테이블이다.
Finally, defnode makes a new node (of either type) and stores it in *nodes*.
마지막으로, defnode는 새로운 노드를 만들어서 *nodes*에 저장한다.
Using these materials we could define the first node of our tree:
이것들을 이용하여 트리의 첫번째 노드를 정의할 수 있다.
;;첫번째 노드 정의하기
(defnode ’people "Is the person a man?" ’male ’female)

이제 네트워크를 만들어자. (여기서 네트워크는 알다시피 IO가 아니라 연결을 자료구조를 말하는 것)
(defnode 'people "Is the person a man?" 'male 'female)
(defnode 'male "Is he living?" 'liveman 'deadman)
(defnode 'deadman "Was he American?" 'us 'them)
(defnode 'us "Is he on a coin?" 'coin 'cidence)
(defnode 'coin "Is the coin a penny?" 'penny 'coins)
(defnode 'penny 'lincoln)

; Figure 6.3: Sample network.
Figure 6.3 shows as much of the network as we need to produce the transcript in Figure 6.1.
Figure 6.3는 처음 시연을 보였던 Fig 6.1를 재연하는 정도의 네트워크를 만든다.

Now all we need to do is write a function to traverse this network, printing out the questions and following the indicated path.
이제 우리가해야 할 일은 이 네트워크를 통과하는 함수를 작성하여 질문을 출력하고 표시된 경로를 따르는 것.
This function, run-node, is shown in Figure 6.4. Given a name, we look up the corresponding node.
이와 같은 기능, run-node는 그림 6.4에 나와 있습니다. 이름이 주어지면 해당 노드를 찾음.
If it is not a leaf, the contents are asked as a question, and depending on the answer, we continue traversing at one of two possible destinations.
잎이 아닌 경우 내용은 질문으로 표시되며 답변에 따라 가능한 두 목적지 중 하나를 계속 탐색.
If the node is a leaf, run-node just returns its contents.
노드가 리프 인 경우 run-node는 내용을 반환.
With the network defined in Figure 6.3, this function produces the output shown in Figure 6.1.
그림 6.3에 정의 된 네트워크에서이 기능은 그림 6.1에 표시된 출력을 생성.
Figure 6.4: Function for traversing networks.

(defun run-node (name) 
  (let ((n (gethash name *nodes*))) 
    (cond ((node-yes n) 
           (format t "~A~%>> " (node-contents n)) 
           (case (read) 
             (yes (run-node (node-yes n))) 
             (t (run-node (node-no n))))) 
           (t (node-contents n)))))

Figure 6.5: A network compiled into closures.
(defvar *nodes* (make-hash-table))

(defun defnode (name conts &optional yes no) 
  (setf (gethash name *nodes*) 
  (if yes 
    #'(lambda () (format t "~A~%>> " conts) 
        (case (read) 
          (yes (funcall (gethash yes *nodes*))) 
          (t (funcall (gethash no *nodes*))))) 
    #'(lambda () conts))))
이렇게 전통적인 방법을 이용보앗다.
1. data structure 정의
2. 분리된 코드로 네트워크 순회

다음 섹션에서 클로저를 이용한 방법을 알아보자.

[on lisp] 5.7 When to Build Functions

Expressing functions by calls to constructors instead of sharp-quoted lambda expressions could, unfortunately, entail unnecessary work at runtime.
#'(lambda ...)대신 생성자 호출을 통한 함수 표현은, 불필요한 일을 런타임에 하도록 한다.
A sharp-quoted lambda-expression is constant, but call to constructor function will be evaluated at runtime.
#'(lambda ...)는 상수다, 하지만 생성자호출 함수는 런타임에 평가된다.
If we really have to make this call at runtime, it might not be worth using constructor functions.
만약 정말로 런타임에 이런 호출을 해야 한다면, 생성자 함수를 사용하는 것은 가치가 없을 수 있다.
However, at least some of time we can call the constructor beforehand.
하지만, 최소한 어떤 때는, 우리는 생성자를 그전에 호출할 수 있다.
By using #., the sharp-dot read macro, we can have the new functions built at runtime.
#., 샤프도트 리드 매크로, 를 이용하면 런타임에 새로운 함수를 가질 수 있게 된다.
So long as compose and its arguments are defined when thie expression is read, we could say, for example:
(find-if #.(compose #'oddp #'truncate) lst)
Then the call to compose would be evaluated by the reader, and the resulting function inserted as a constant into our code.
compose에 대한 호출은 리더에 의해 평가되고, 컴파일 되면 상수로 코드에 들어가게 된다.
Since both oddp and truncate are built-in, it would safe to assume that we can evaluate the compose at read-time, so long as compose itself were already loaded.
oddp, truncate 모두 built-in이기 때문데, read-time에 compose를 평가해도 안전하다는 것을 예측하고, 그렇기 때문에 compose자신은 이미 로드가 되었음을 알 수 있다.
In general, composing and combining functions is more easily and efficiently done with macros.
일반적으로, composing/combining함수들은 매크로를 이용하면 더 쉽고 효율적이다.
This is particularly true in Common Lisp, with its separate name-space for functions.
특히 커먼리습에는 함수와 다른 네임스페이스를 가진다.
After introducing macros, we will in Chapter 15 cover much of the ground we covered here, but in a more luxurious vehicle.
매크로는 15챕터에서 소개한다.

2019년 8월 23일 금요일

[on lisp] 5.2 Orthogonality (직교성)

An orthogonal language is one in which you can express a lot by combining a small number of operators in a lot of different ways.
직교성이 있는 언어는 작은 여러 연산자들을 이용하여 많은 것들을 표현할 수 있다.(그리고 그 방법 또한 여러가지임)

Toy blocks are very orthogonal; a plastic model kit is hardly orthogonal at all.
레고블록은 직교성이 있지만, 특정 모델 키트는 직교적이지 않다.
The main advantage of complement is that it makes a language more orthogonal.
complement함수의 주된 장점은 언어를 더욱 직교적으로 만든다. (complement는 반대를 말함. (보수))

complement, 커먼리습은 함수의 쌍을 가진다. remove-if/remove-if-not, subst-if/subst-if-not 같은 것.
complement와 함께먼 이것들의 반은 필요 없게 된다.

The setf macro also improves Lisp's orthogonality.
setf 매크로 또한 Lisp의 직교성을 향상시켜준다.
Earlier dialects of Lisp would often have pairs of functions for reading and writing data.
리스프의 초기 방언들은 종종 데이터를 읽고 쓰는 기능을 가지고 있었다.
With property lists, for example, there would be one function to establish properties and another function to ask about them.
예를들어, 프로퍼티 리스트를 함수로 간주하여 거기에다가 값을 넣고 뺄 수 있다고 해보자.
프로퍼티 리스트에는 값을 넣고 빼는 기능이 각각 하나씩 있을 것이다.
In Common Lisp, we have only the latter, get.
커먼리스프에는 후자만 있다.(빼는 기능) get

To establish a property, we use 'get' in combination with setf:
프로퍼티를 설정하려면 setf와 함께 'get'을 사용한다.
(setf (get 'ball 'color) 'red)
(print (get 'ball 'color))
'red
이렇게 넣을 수 잇다.


We may not be able to make Common Lisp smaller, but we can do something almost as good: use a smaller subset of it.
우리는 커먼리습을 작게 만들 수는 없을 것이다, 하지만 거의 그렇게 할 수 있다:작은 집합을 사용하는 것.
Can we define any new operators which would like complement and setf, help us toward this goal?
setf나 complement같은 연산자를 더 만들면 우리의 목표에 더 가까워질까?

There is at least one other way in which functions are grouped in pairs.
기능이 쌍으로 그룹화되는 다른 방법이 최소한 하나 이상있다.
Many functions also come in a destructive version: remove-if/delete-if, reverse/nreverse
한 가지 기능에서 destructive version(리턴하지 않고 값을 바꿔버리는)을 직교 기준으로 보고 여러개의 함수를 제공할 수도 있다. remove-if/delete-if, reverse/nreverse 그리고 append/nconc
처럼 이번에는 그룹의 방식의 기능의 정반대강 아니라 destructive냐 아니냐로 나눈것.
By defining an operator to return the destructive counterpart of a function, we would not have to refer to the destructive functions directly.
이제 특정 함수에 대해서 destructive counterpart를 리턴하는 함수를 함수를 정의하여, 직접적으로 destructive함수를 부를일 없게 만들자.(기준을 만들고 반으로 잘라서 없애버리는 거다)

어떻게? Fig5.1을 보면서 이야기 하자.
;; FIg5.1 : Returning desturctive equivalents
(defvar *!equivs* (make-hash-table))

(defun ! (fn)
  (or (gethash fn *!equivs*) fn))

(defn def! (fn fn!)
  (setf (gethash fn *!equivs*) fn!))
그림5.1은 descturvie counterparts 개념을 지지하는 코드를 가지고 있다.
글로벌 해시테이블 *!equivs* 맵은 destructive equivalents를 위한 기능을 한다.
!함수는 destructive equivalent를 리턴한다.
def!는 *!equivs*에 값을 저장한다.

The name of the ! (bang) operator comes from the Scheme convention of appending ! to the names of functions with side-effects
이름에 !를 붙이는 연산자는 스킴에서 따온 건데 사이드이팩트가 있으면 붙이는 것이다.

자 어떻게 쓰는지 보자.
(def! #'remove-if #'delete-if)
delete-if를 없애버린 것이다. 이제 remove-if만으로 개발이 가능하게 되었다.
아래를 보자.
(delete-if #'oddp lst) ;; 기존에는 이렇게 썼다. 이제 remove-if만으로 개발이 가능하다.

(funcall (! #'remove-if) #'oddp lst)

(defvar *!equivs* (make-hash-table))

(defun !- (fn)
  (or (gethash fn *!equivs*) fn))

(defun def!- (fn fn!)
  (setf (gethash fn *!equivs*) fn!))

;; test
(setf lst '(1 2 3 4 5))
(def!- #'remove-if #'delete-if)


(print (remove-if #'oddp lst))
(print lst)

(print (funcall (!- #'remove-if) #'oddp lst))
;; or ((!- #'remove-if) #'oddp lst)
(print lst)
자 이렇게 새로운 직교규칙을 만들어서 쪼개버린다. 그렇게하여 작은언어 아니 작은 집합으로 쪼개진 언어를 가지게 된다.
(더 작은 블록은 가지게 된 것같은 느낌인데? 이렇게 desctructive에 대한 함수가 각각 두개씩 있는 것에는 블록에 유형이 있을 것이다.
우리는 그것이 뭔지는 모르지만 이렇게 임의로 쪼개버릴 수 있다.)
추가로 나는 online compiler에서는 !(느낌표)를 정의할 수 없어서 !-로 했다.

As well as greater orthogonality, the ! operator brings a couple of other benefits.
직교성을 높인 것 뿐만 아니라. !연산자는 다른 이점을 준다.
It makes progames clearer, because we can see immediately that (! #'foo) is destructive equivalent of foo.
프로그램을 깔끔하게 한다. (! #'foo)를 보면 바로 foo의 destructive버전인 것을 알 수 있다. (remove-if/delete-if는 헷갈릴 수밖에 없다)
Also, it gives destructive operations a distinct, recognizable form in sourece code, which is good because they should receive special attention when we are searching for a bug.
또한 destructive 연산자에 뚜렷하고, 눈에띄는 소스코드 형태를 제공한다. 이렇게 함으로써 버그를 찾을 때, 특별한 주의를 줘야 하는 부분(이렇게 값이 변하는 곳)을 쉽게 찾을 수 있게 된다.

Since the relation between a function and its destructive counterpart will usually be known before runtime,
destructive에 직교성에 대한 함수 관계까 런타임 이전에 알 수 있기 때문에
it would be most efficient to define ! as a macro, or even provide a read macro for it.
!를 매크로로 만드는 것이 유용할 것이다, 또한 더 나아가 이것을 위한 read macro를 제공한다. (여기서 read macro는 뭔지 모르겠음 아직 안나온 이야기)

직교성이 이렇게 중요한 것이었구나...
라는 생각을 한다.

2019년 8월 1일 목요일

[책리뷰] An Introduction to Functional Programming Through Lambda Calculus 를 읽고

이 책은 이번해에 읽은 책 중에 단연 최고의 책이라고 말하고 싶다.

정말 많은 책을 읽었는데 이 책 만큼 간결하고, 글에 군더더기가 없고, 정확히 설명해준 책은 없었다.

IT서적에는 수 많은 설명과 자신만의 유머를 덧붙이거나, 짧게 정제된 글만을 적어놓은 책이 극단으로 나뉘는 경우가 많은데

이 책은 후자에 속한다.

lambda calculus에 대해 좀 더 자세하게 알게 된다. 그리고 람다. 그 람다라는 것으로 무엇을 할 수 있는지 알려준다.

람다를 시작으로 숫자를 표현할 수 있고, 숫자를 더하거나 곱하거나 뺄 수도 있다.

신기한 것은 숫자 자체도 람다로 표현이 된다는 것이다.

타임또한 람다에 녹여서 사용할 수 있다.

더욱 무시무시한건 이 책이 Introduction이라는 것이다. 람다와 함수형 프로그래밍에는 이 것을 넘어서는 기법들이 많은 것같다.

이 책은 나의 생각을 넓혀주었다. 엄청난 책이다.