2019년 10월 27일 일요일

[리뷰][맨먼스미신]을 읽고

이 책은 저자가 한 프로젝트를 진행하면서 겪었던 내용을 에세이로 적은 책이다.
이 책은 소프트웨어 개발자의 이야기라기보다 인간들의 이야기이다.
우리 인간들이 일을 함께 하면서 일어나는 일이다. 용어들만 바꾼다면 어떤 산업에서도 적용될 이야기이다.

이 책에서 그 유명한 대사가 나오는데 바로
여자 9명이 있다고 아기가 1개월만에 나오는 것이 아니다.

이 대사가 우리가 일을 할 때, 무엇을 생각하고 계획해야 하는지 알려주는 것이라 생각한다.

내용

이 책은 프로젝트를 어떻게 관리해야 하는지 설명한다. 그는 자신이 믿지는 않지만 개발자의 경력과 생산성에는 큰 차이가 없는 연구결과를 지속적으로 알려준다.(믿지않으면서 왜 지속적으로 보여주는 거지)
아마 우리가 생각하는 통념을 조금이나마 깨뜨리려고 하는 것이 아닌가.
나에게 가장 기억에 남는 것은 일반적으로 개발을 해서 앱을 만드는 것과 그 앱을 상용화하기 위한 제품으로 만드는 것에는 아주 큰 차이가 있다는 것이다.

생산성 측면에서 보면 적은 인원의 외과의(스타개발자)가 최고의 성능을 가져오면 버그율도 적다. 하지만 큰 프로젝트를 진행하려면 시간이 아주 오래 걸릴 것이다.
그래서 사람이 더 필요하게 되는데 사람이 더 많아질 수록 의사소통, 교육 등에 병목이나, 마찰율(책에는 없는 단어다 ㅋㅋ)같은 것이 발생하여 진행이 많이 떨어지게 된다는 점을 말한다.
하지만 상용제품은 그밖에도 다른 것들에 문제가 있는데, 바로 요구사항 적립의 체계화, 테스트, 문서화, 재무관리 등으로 실제 개발시간에 수배의 노력(책에서는 9배라고 나온 것으로 기억한다.)이 필요하다는 것이다.

외과 수술 팀이라는 형식으로 팀을 꾸릴 것을 제안한다. 외과의과 나머지라고 부를 수도 있겠다. 바로 집도하는 외과의를 필두로 그를 보조하는 사람들을 분야별로 나눠서 구성하는 것이다.
재무, 프로그래밍언어, 새로운 툴 등을 분야별로 나눠서 처리하여 외과의를 보조한다. 신선한 방식이다. 아직 개발을 하면서 이런 형태를 본 적이 없어서 어떨지는 잘 모르겠지만, 좋을 것 같긴한다.

그리고 뒷부분으로 갈수록 문서화에 대한 내용에 노력을 기울인다. 주목할만한 점 중에 하나는, 개발을 하는데 서로의 이슈나, 요구사항 변경내용을 모두 적어서 모든 담당자들이 보도록 해서 서로 무슨일을 하는지 공유하는 것이 중요하다고 했지만,
종단에는 시간이 지나서 자신이 틀렸다고 말한다. 서로가 무슨 일을 하는지 모르고 자신의 업무에 집중하며 연결되어야 하는 부분에만 집중하는 것이 더 효율적이라고 주장이 대두되었고, 그는 그에 동조한다.

또한 논란이 되었던 은 탄환은 없다는 소프으웨어 개발에 지름길이란 없다는 것을 서술한다.
소프트웨어가 복잡한 것은 그 내재된 성질이기에, 도구로 고칠 수 있는 것이 아니라는 것이다. 객체지향 언어나 다른 방법론들이 쏟아져 나오지만, 결국 복잡성은 밖에 문제가 아닌, 소프트웨어 개발 자체가 가진 성질이며, 그것들을 없애주는 은탄환은 없다고 말한다.

이 책은 개발보다는 소통에 관한 내용이 많다. 그래서 누구라도 읽어볼만 하다고 할 수 있다.

[리뷰][클린 아키텍처]를 읽고

클린 아키텍처는 밥 아저씨의 클린 시리즈에서 가장 최신작인 것 같다.
이 책은 이전에 읽었던 [클린코드]와는 달리 아주 멀리서 소프트웨어산업을 바라보고 자신의 생각을 담아내고 있다.

내용

초반에는 자신이 생각하는 소프트웨어에 대해 설명하며 무엇이 소프트웨어를 더욱 소프트하게 만드는지 알려준다.
소프트함이라는 것은 변하기 쉬워야 한다는 것이다. 변하기 어렵게 만들거였으면, 하드웨어로 만들었을 것이라는 것이 그의 주장이다.
극단적인 예로, 변하기 쉽지만 요구사항에 맞지 않는 프로그램과 요구사항에는 맞지만 변하기 어려운 프로그램을 주어지면 그는 주저없이 첫번째를 선택하겠다고 말한다.
변하기 쉬운 것은 요구사항에 맞출 수 있지만, 후자는 비즈니스 로직이 변하거나, 버그를 발견했을 때, 바꾸기 어려우므로 안된다는 것이다.
물론 전대 변할 수 없는 프로그램은 없다. 하지만 프로그램을 변화시켜서 기대되는 비용과 개발을 하는 비용의 수지타산이 맞지 않을 때, 변할 수 없는 프로그램이라 부를 수 있다면 그런 프로그램은 존재할 것이다.

중반부터는 어떻게 하면 안전한 코드를 만드는지 설명한다. 하지만 코딩자체를 말하지는 않는다. 오히려 컴포넌트나 서비스같은 단위로 설명한다. 하지만 이 모든 내용은 작게는 코드에 대한 내용과 별 다를 것이 없어보인다.
그는 객체지향에 대해 설명을 하지만, 객체지향에 어떤 것도 이전에 C로 코딩을 하던 개발자들을 더 나은 개발자로 만들지는 못했다는 것을 설명한다. 왜냐하면 당시 개발자들 모두 그렇게 개발을 했었기 때문이다.
상속, 캡슐화, 다형성 모두 C언어로 가능한 구현들이었다. 대부분 문법적으로 간단하게 해준 것이며, 그 마저도 어떤 것은 오히려 구현이 불안정하게 만들었다. 저자가 객체지향으로 넘어가면서 얻은 장점은 다형성이라고 주장한다.

그리고는 지속적으로 그래프를 보여주면서 컴포넌트의 의존성의 방향이 얼마나 중요한지 설명한다. 나는 처음 이런 것을 보면서, 저자의 깊이를 더 알게 되었고, 정말 나중에 한 번 더 읽어야 겠다는 생각을 했다.
더해서 DI라는 것이 어떻게 아키텍처에서 쓰이는지 알게 되었다. 아무때나 DI를 다 붙잉는 것이 아니라. 이 방향을 바꾸기 위해서 하는 것이라는 것을 알게 되었다.
더해서 컴포넌트를 분리할 때도 DI를 사용해서 A,B컴포넌트에 서로 로직들이 잘 분리되도록 하는 것이다.

확실히 저자는 다형성에 큰 의의를 두고 있었다.

또 기억나는 것은 바로 구현에 대한 결정은 나중으로 미루라이라는 것이다. 그는 지속적으로 플러그인 패턴을 강조했다. 예를들어, 출력되는 곳이 콘솔일지, 프린터일지, HTML일지 이런것은 나중에 결정하라는 것이다.
환경은 또 어떨까? 웹으로 할 것인지 아닌지도 나중에 결정할 수 있도록 해야한다. 영속성은? DB로 할지도 나중에 결정해야 한다. 단순히 오라클/MySQL/... 중에 뭘 고를지 정하는 것이 아니다.
처음에는 map자료구조에 메모리로 저장하여 개발함으로써 영속성의 결정을 미룬다. 그리고 파일로 저장해서 또 미룬다. 미루고 미룬 후에 결정하는 것이다. 저자는 때때로 DB를 사용하지 않고 파일로 저장하고 가져와서 로딩하는 방식을 사용했다.
왜냐하면 DB가 필요 없었기 때문이다. 파일로도 충분했다.

이 구현을 미루는 것은 아키텍처에게 유현함(soft)함을 가져오게 해주고, 이런 외부 사항들로과 비즈니스로직을 자연적으로 분리시켜주게 된다. 그는 software가 firmware가 되는 것을 싫어했다. 펌웨어는 한 하드웨어에 종속되어 움직이는 경우가 대부분인데
그의 관점에서는 특정 DB에 종속된 것, 아니면 DB에 종속된 것을 펌웨어라 간주한다. 왜냐하면 세상은 많이 복잡해졌고, 그 정도의 종속도 펌웨어라는 것이다.
HTTP에 종속된 것도 펌웨어이다. 그는 코어 로직이 어딘가에 종속되지 않기를 바란다.

예를 들어보면 아래와 같다.

Core <- DB-AccessObject -> DB
     <- Network-Object
여기서 화살표 방향은 해당 객체가 무엇을 사용하는지 알려준다. 왼쪽에 있는 Core컴포넌트는 아무것도 사용하지 않아야 한다. 그래야 플랫폼이 바뀌어도 사용할 수 있다. DB를 사용하기로 결정했어도 Core는 변경되지 않는다. 그런데 말이 되지 않는다. 만약 Network에서 요청이 오고 DB-AccessObject를 실행해야 하는데 어떻게 Core에서는 사용하지도 않는데 실행을 한다는 거지? 그 답은 아래 DI에 있다(!!) 아래처럼 화살표가 되어 있다고 해보자.
Core -> DB-AccessObject -> DB
     <- Network-Object
이렇게 구현하는 것이 자연스러울 것이다. 여기서 DI를 적용하면?!
Core -> I-Persistent(in Core Component) <- DB-AccessObject -> DB
     <- Network-Object
이렇게 I-Persistent를 이용하여 저장하고 조회하는 것이다. I-Persistent는 코어 컴포넌트 안에 있고, 영속성관리를 무엇으로 해도 상관이 없게 되었다.
이것이 DI의 힘이다. 나는 지금까지 재사용을 쓰기 위해서다. 구현이 바껴도 사용할 수 있어서 DI를 쓴다고 알고 있었다. 물론 맞는 말이긴 해도, 이정도의 깊이가 있는 사람이 동일한 말을 하는 사람과는 전혀 달랐다.
나는 이정도의 넓은 숲을 보면서 말하지는 않았다. 왜냐하면 이런 세상을 전혀 몰랐으니까...

방향을 바꿔서 컴포넌트의 분리를 꾀하고, 종단에는 코어를 (유연하게 하는 것)지키는 것.
일류 아키텍처의 속마음을 잠깐 들여다 볼 수 있는 순간이었다.
마지막으로 가면 갈수록 그는 이 DI를 더 강조하는 것처럼 보인다. 그리고 실제적인 예시들을 보여주면서 어떻게 해야 하는지 알려 준다. 사실 다 읽었지만, 완벽하게 이해했다고 생각되지는 않는다.

나에게 또한 많은 생각을 하게 한 내용은 바로 프레임워크다.
그는 프레임워크에 종속되면 안된다고 말한다. 물론 프레임워크 개발자는 당신이 종속되도록 요청한다. 아예 하나의 언어처럼 붙어살길 원한다. 하지만 당신은 그렇게 하면 안된다는 것이다.
당신은 프레임워크를 툴로 써야 하며, 언제든지 바꿀 준비가 되어있어야 한다. 도구로써 써야 한다. 마법과 같은 일을 할 수 있다고 계약하지 말라.
당신에게 분명 되돌아 온다. 프레임워크에 종속되어 펌웨어가 되지 말라고 한다.

예를들어 자바의 스프링프레임워크를 예를 들면, Autowired를 하지말라고 한다. 차라리 Main에 다가 종속성을 다 쓰라고 말한다. 당신이 사용하는 모든 클래스에 spring이 import되어 있다면
종속되어 있는 것이다. 어쩌면 플러그인 패턴을 사용하여 DI로 프레임워크를 분리해서 사용하는 것이 해답이 될 수도 있을 것 같다.

끝없이 소프트하게 하라, 그것이 소프트웨어 개발자가 해야 할 일인 것이다.

이 책은 분명 다시 읽어야겠다. 누군가가 개발자가 되었다면, 그리고 소프트웨어개발자라면, 언젠가는 읽어야 할 책이라고 소개하고 싶다.

2019년 10월 25일 금요일

[리뷰][let over lambda]를 읽고-1

이 책은 [on lisp]를 보다가 머리를 식힐 겸 구매한 책이다.
다들 [on lisp]를 먼저 보라고 추천을 했지만, 어느정도 [on lisp]로 쉬운부분(매크로 초반까지) 읽고 [let over lambda]로 가는 것이 더 좋지 않을까 라는 생각을 한다.
[on lisp]보다 쉽다고 개인적으로 생각한다. [on lisp]는 아직 끝내지도 못했다... 무서워서... (일단 [on lisp]는 예제 코드가 너무 무섭다. 너무 길고 하나하나 쓰면서 이해하는데 시간이 너무 든다. 하지만 분명 나중에 정복해야 하는 책임은 분명하다.)

이 글은 단순한 감상이며 나중에 좀 더 심도 있는 내용을 적어야 할 것같다.

줄거리

그나저나
왜 이름이 [let over lambda]인가?
이것은 리스프에서 아주 기본적으로 사용되는 패턴으로 let으로 lambda를 덮는 형태를 말한다.
;; common lisp
(let ((a 10))
  (+ a 1))
;= 11
그러므로 [let over lambda]를 클로저라는 이름을 리습세상의 언어를 이용하여 우아하게 표현한 것이다.
이 [let over lambda]가 중요한 이유는 pointer를 조작하는 또 다른 방식이 될 것이기 때문이다.
이 let에서 생성되는 포인터를 이용하여 리스프 세상에서는 더 멋지고 자유롭게 표현할 수 있는 여지를 가지게 된다.
이것은 특히 커먼리습에서 대두된다.

이 책은 단순히 클로저만을 소개하는 책이 아니다. 시작이다. 이 책은 매크로를 설명한다. 이 책은 매크로로 어디까지 할 수 있는지 조목조목 설명해준다.
특히 뒤로 갈 수록 그 난이도는 올라가지만, 더 흥미로움은 더욱 커진다.

가장 충격적인 것은 Name Capturing이다. 이름이 충돌하는 문제인데, 이것은 람다에서도 나오는 베타-리덕션을 하다가 동일한 이름을 쓰는 매개변수가 있는 경우 리덕션에 문제가 생기는 경우와 비슷하다.
람다 계산의 경우 알파-컨버젼을 이용하여 시퀀스를 뒤에 붙이는 경우와 비슷하다.

책에서는 (gensym)으로 컴파일러가 내부적으로 현재 사용하고 있는 이름들을 확인하고 그들과는 겹치지 않는 이름을 만들어서 충돌이 일어나지 않도록 하는 방법을 제시한다.
하여 아래 코드는 절대로 참이 나올 수 없다.
왜냐하면 이 코드를 확인 한 후에 겹치지 않도록 이름을 제시해줄 것이기 때문이다.
(equal (gensym) (gensym))

이 서적은 더 나아가서, Name Capturing을 일부러 만드는 방식을 제시한다.
마치 큰 파도가 밀려오면 키를 돌려 정면으로 돌파해야 하는 것처럼.
Name Capturing을 피하는 것이 아닌, 정면 돌파하여 더 우아한 언어로 만들어주는 것이다.
마치 원래 그 언어가 있었 던 것처럼...
나중에 다뤄야 겠다.

후기

[let over lambda]는 이전에 람다계산 관련 책에 이어서 이번해에 읽은 최고의 책 중에 하나로 선정하겠다.
이번해에도 꽤나 많은 책을 읽었는데
기억에 남는 책은 이 두 권이라니... 나의 선택능력은 꽝인 것 같다.

꼭 읽어야 하는 책이다. 이 책을 읽기 위해서 common lisp, scheme, clojure 같은 언어를 둘러 본다해도
시간이 아깝지 않을 것이다.

2019년 10월 2일 수요일

[리뷰] lambda calculus에 대해 알아보기

https://www.notion.so/tombox/Lambda-Calculus-aa7f35c6a6ec4ecb9288e2bd33493dc8
lambda calculus에 관하여 이 책을 일독하였으나 기억이 가물가물해져서
다시 읽으면서 Notion에 적어보고 있다.

다시 읽어도 새롭다. 엄청난 책임이 틀림없다. 그리고 시간이 나면 Combinator에 대한 책도 읽고자 한다.

2019년 9월 11일 수요일

[clojure programming] 2. Functional Programming - 로깅파일 만들기

출처 : Clojure programming
Building a Primitive Logging System. with Composable Higher-Order Functions

(defn print-logger [writer]
  #(binding [*out* writer]
     (println %)))

(def *out*-logger (print-logger *out*))
(*out*-logger "hello")

버퍼를 만들어 보자.
(def writer (java.io.StringWriter.))
(def retained-logger (print-logger writer))
(retained-logger "hello-1")
(retained-logger "hello-2")
(str writer)

파일에 써보자.
(require 'clojure.java.io)
(defn file-logger [file]
  #(with-open [f (clojure.java.io/writer file :append true)]
      ((print-logger f) %)))

(def log-file (file-logger "message.log"))
(log->file "HELLO FILE")
지금까지는 아주 적은 코드로 서로 다른 곳에 쓸 수 있는 방법을 알아봤다.
만약, 여러곳에 써야 한다면 어떻게 해야 할까. doseq를 쓴다.
(defn multi-logger
  [& logger-fns]
    #(doseq [f logger-fns]
       (f %)))
(def log (multi-logger
           (print-logger *out*)
           (file-logger "message.log")))
(log "hello console and file")

마지막 예제, 진짜 로깅인 것처럼 시간 포맷까지 앞에 붙여보자.
Adding a piece of "logging middleware" to include a timestamp with each log message
(defn timestamped-logger
  [logger]
  #(logger (format "[%1$tY-%1$tm-%1$ts %1$tH:%1$tM%1$tS] %2%s" (java.util.Date.) %)))

(def log-timestamped (timestamped-logger
                      (multi-logger
                        (print-logger *out*)
                        (file-logger "message.log"))))
(log-timestamped "goobye, now")

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

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

[on lisp] 12.3 new utilites

;; 12.3 New Utilities
; 이번에는 generalized variables에 관련된 새로운 유틸리티들을 볼 예정.
; 인수를 그대로 [setf]에 전달하려면 매크로 여야함.
; Fig 12.1는 setf을 이용한 예제를 보여줌.

(setf x nil y nil z nil)
;; 이럴거면 이제
(nilf x y z)
;; 이렇게 쓸 수 있다.
;; 이 4개의 매크로는 꽤나 중요한 점을 보여준다. 바로 값을 할당하는 연산자를 보여준 것이다.
;; Even if we only intent to use an operator on ordinary variables, it's worth wrting it to expand into a [A] instead of a [B]
; 일반 변수에만 연산자를 사용하려는 경우에도, setq보단 setf로 확장하도록 작성하는 것이 좋다.
; setq는 set qoute의 약자며 setf는 set function의 약자다
; q는 저장될 첫번째 매개변수로 이름만 올 수 있지만, setf는 함수도 올 수 잇다.

; Fig 12.2 에는 리스트의 끝을 파괴적으로 수정하기위한 3개의 매크로가 포함되어 있다.
; nconc란 사이드이펙트를 일으키는 conc (합치기) 첫번째 매개변수에 값이 더해지는 방식 (새로운 객체를 리턴하는게 아니라)

;; Fig 12.2 List operations on generalized variables.
(define-modify-macro concf (obj) nconc)

(define-modify-macro conc1f (obj)
(lambda (place obj)
(nconc place (list obj))))

(define-modify-macro concnew (obj &rest args)
(lambda (place obj &rest args)
(unless (apply #'member obj place args)
(nconc place (list obj)))))

; 우리는 이전에 (nconc x y)가 사이드이팩트를 일으킨다고 이야기 했다. 그리고 아래처럼 작성해야 한다.
(setq x (nconc x y))
;; 이런 형태를 매크로로 만드는 것이다. 그것이 concf다.
; conc1f는 새로운 요소를 리스트 뒤에 붙인다.
; concnew는 conc1f와 같지만 오로지 멤버에 없을 경우만이다.

;; 섹션 2.2에서 함수는 lambda-expression일 뿐만 아니라 symbol이 될 수 있다 했다.
; Thus it is find to give a whole lambda-expression as the third argument to [define-modify-macro], as in the definition of [conc1f]
; 따라서 define-modify-macro의 세번째 매개변수로 람다표현식 전체를 넣어도 된다.
; conc1 (45페이지)의 내용을 쓰자.
; page45 (Fig4.1 Small functions which operate on lists)
(defun conc1 (lst obj)
(nconc lst (list obj)))

(define-modify-macro conc1f (obj) conc1)
; conc1f라는 이름에
; define-modify-macro는 3개의 매개변수를 받는다.(다시씀)
; 1, name of the macro
;2. its additional parameters (after the generalized variable),
;3. the name of the function which yields the new value for the generalized variable

[on lisp] 12.2 The Multiple Evaluation Problem

;gnu clisp 2.49

(print "Hello, world!")

(defmacro toggle (obj)
    `(setf ,obj (not ,obj)))


(print
;; 빡치게 두번 실행됨. 한번만 실행되어야 하는데
;; 토글 안에서 obj를 두개 넣기 때문임.
(let ((lst '(t nil t))
      (i -1))
     (toggle (nth (incf i) lst))
     lst)
 )
(T NIL T) ; 안됨 첫번째 T가 NIL이 되어야함.
;; 형태를 더 보면
(setf (nth (incf i) lst)
      (not (nth (incf i) lst)))
;; 이렇게 (incf i)를 두번 부르게 된다.

;; 'toggle'의 표현식 인수를 단순히 'setf'의 첫 번째 인수로 삽입하는 것만으로는 충분하지 않다.

; 우리는 들어온 표현식의 내부를 확인해봐야 한다.
; 하위 양식이 포함 된 경우, 부작용이있는 경우 하위 양식을 분리하여 개별적으로 평가해야합니다.

; 쉽게 만들기 위해. Common Lisp는 setf에서 제한된 매크로 클래스를 자동으로 정의하는 매크로를 제공.
; 이 매크로가 setf의 제한된 매크로 클래스 인 'defined-modify-macro'.
; 이름, 추가적인 매개변수(after the generalized variable), 그리고 함수이름 3개의 매개변수를 받는다.

; Using define-modify-macro, we could define toggle as follows:
; (define-modify-macro toggle () not)
; 이 코드는 "to evaluate an expression of the form (toggle place), find the location specified by place,
; and if the value stored there is val, replace it with the value of (not val)"이다.
; (toggle place)형태의 표현식을 평가하려면, place로 특정되는 위치를 찾고, 값이 있다면 그걸 val로 하자, 그 val을 (not value)로 치환하라.
(define-modify-macro toggle () not)
;; 이렇게만 하면 된다. 테스트해보자.
(let ((lst '(t nil t))
      (i -1))
     (toggle (nth (incf i) lst))
     lst)
(NIL NIL T) ; 잘됨

;; 좀더 일반화 할 수 있다.
;setf와 setq는 임의의 갯수의 인수를 취할 수 있으므로 toggle도 마찬가지로 가능할 것이다.
; modify-macro 위에 다른 매크로를 정의하여이 기능을 추가 할 수 있습니다.
;Fig 12.1로 더 자세히 보자.
(defmacro allf (val &rest args)
    (with-gensyms (gval)
        `(let ((,gval ,val))
              (set ,@(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)
;; 이건 이전에 만든 토글
(defmacro toggle (obj)
    `(setf ,obj (not ,obj)))

[on lisp] 11.5 Iteration with Multiple Values (mvdo*)

11.5 Iteration with Multiple Values
fig 11.10을 보자
(defmacro mvdo* (parm-cl test-cl &body body)
  (mvdo-gen parm-cl parm-cl test-cl body))

(defun mvdo-gen (binds rebinds test body)
  (if (null binds)
      (let ((label (gensym)))
        `(prog nil
           ,label
           (if ,(car test)
               (return (progn ,@(cdr test))))
           ,@body
           ,@(mvdo-rebind-gen rebinds)
           (go ,label)))
      (let ((rec (mvdo-gen (cdr binds) rebinds test body)))
        (let ((var/s (caar binds)) (expr (cadar binds)))
          (if (atom var/s)
              `(let ((,var/s ,expr)) ,rec)
              `(multiple-value-bind ,var/s ,expr ,rec))))))

(defun mvdo-rebind-gen (rebinds)
  (cond ((null rebinds) nil)
        ((< (length (car rebinds)) 3)
         (mvdo-rebind-gen (cdr rebinds)))
        (t
         (cons (list (if (atom (caar rebinds))
                         'setq
                         'multiple-value-setq)
                     (caar rebinds)
                     (third (car rebinds)))
               (mvdo-rebind-gen (cdr rebinds))))))
mvdo*를 써보자
(mvdo* ((x 1 (1+ x))
        ((y z) (values 0 0) (values z x)))
       ((> x 5) (list x y z))
  (princ (list x y z)))
(1 0 0)(2 0 2)(3 2 3)(4 3 4)(5 4 5)
(6 4 5)
와... 이렇게 여러개 반복이 멋지게 된단 말이다. 이런 식의 반복은 꽤나 유용하다. 그래픽 프로그램에서는 종종 좌표나 지역 같은 다중 값들을 한번에 다뤄야 한다. 단순한 대화형(반응형) 게임을 작성한다 할때, 목표는 두 물체 사이에 찌그러지지 않도록 하는 것이다. 만약 두 물체가 동시에 당신을 맞추면 지는 것. 혹은 두 물체가 서로를 부신다면 이기는 것. 그림 11.11 에서 이런 게임을 만들 때 mvdo*가 얼마나 유용할지 보여줄 것.
(mvdo* (((px py) (pos player) (move player mx my))
        ((x1 y1) (pos obj1)   (move obj1 (- px x1)
                                         (- py y1)))
        ((x2 y2) (pos obj2)   (move obj2 (- px x2)
                                         (- py y2)))
        ((mx my) (mouse-vector) (mouse-vector))
        (win     nil          (touch obj1 obj2))
        (lose    nil          (and (touch obj1 player)
                                   (touch obj2 player))))
       ((or win lose) (if win 'win 'lose))
   (clear)
   (draw obj1)
   (draw obj2)
   (draw player)) 
; (pos obj) return two values x, y representing the position of obj. ?Initially, the three objects have random positions
; (move obj dx dy) moves the obj depending on its type and the vector(dx,dy). returns two values x,y indicating the new position.
; (mouse-vector) returns two values dx, dy indicating the current movement of the mouse.
; (touch obj1 obj2) returns true if obj1 and obj2 are touching
; (clear) clear the game region
; (draw obj) draws obj at its current position
아래는 코드를 분석해본 것
(defmacro mvdo* (parm-cl test-cl &body body)
  (mvdo-gen parm-cl parm-cl test-cl body))

(defun mvdo-gen (binds rebinds test body)
  (if (null binds) ; binds가 없다면(재귀 다돌면)
      (let ((label (gensym)))
        `(prog nil
           ,label
           (if ,(car test)
               (return (progn ,@(cdr test))))
           ,@body
           ,@(mvdo-rebind-gen rebinds)
           (go ,label)))
      (let ((rec (mvdo-gen (cdr binds) rebinds test body)))  ;; binds가 있다면. (mvdo-gen (cdr binds) rebinds test body)를 rec로 부른다. (재귀할건가봄)
           (let ((var/s (caar binds)) (expr (cadar binds)))  ;; binds의 첫번째의 심볼값을 var/s라 하고 그 옆에 있는 녀석을 expr로 넣는다.(초기값)
          (if (atom var/s)  ; var/s가 하나라면 (다중이 아니라면)
              `(let ((,var/s ,expr)) ,rec) ; (,var/s ,expr) 로 값을 바인딩함 그 후 rec를 실행 (mvdo*는 순서대로 실행되어야함 바인딩이 순서가 있음)
              `(multiple-value-bind ,var/s ,expr ,rec)))))) ; 여러개면 multiple-value-bind를 쓴다.  (multiple-value-bind (...) (...) rec) rec에서 let과 muliple-value-bind가 계속 만들어짐
; 결국 재귀를 하다가 (if (null binds)...) 에 다다르면
; (prog를 쓰고 (return (progn ,@(cdr test)))로 리턴할 값을 리턴함.
; cadar는 cdr후 car한것
; 데모
'(mvdo* ((x 1 (1+ x))
        ((y z) (values 0 0) (values z x)))
       ((> x 5) (list x y z))
   (princ (list x y z)))
))
;(LET ((X 1))
; (MULTIPLE-VALUE-BIND (Y Z) (VALUES 0 0)
;  (PROG NIL #:G3210 (IF (> X 5) (RETURN (PROGN (LIST X Y Z)))) ; PROG NIL(바인딩없음) #:G3210 고투문을 위한 이름
;   (PRINC (LIST X Y Z)) ; ,@body
;   (SETQ X (1+ X))  ; ,@(mvdo-rebind-gen rebinds)
;   (MULTIPLE-VALUE-SETQ (Y Z) (VALUES Z X)) (GO #:G3210))))  ; (go ,label)))


; rebinds가 nil이면 nil
; (< (length (car rebinds)) 3) 길이가 3보다 작으면(2개면 값설정만 3개면 루프돌때마다 업데이트) (mvdo-rebind-ged (cdr rebinds))
; 
(defun mvdo-rebind-gen (rebinds)
  (cond ((null rebinds) nil)
        ((< (length (car rebinds)) 3)
         (mvdo-rebind-gen (cdr rebinds)))
        (t
         (cons (list (if (atom (caar rebinds)) ; 바인딩될 값이 atom이면 setq
                         'setq
                         'multiple-value-setq) ; 여러개면 multiple-value-setq
                     (caar rebinds)  ; caar로 첫번째(현재바인딩문)의 첫번째 빼냄.(심볼)
                     (third (car rebinds))) ; 업데이트문 빼냄.
               (mvdo-rebind-gen (cdr rebinds)))))) ; cdr로 다음타자 재귀로 부름. 마지막에 cons로 합침.
; 데모
(print (macroexpand
        (mvdo-rebind-gen '((x 1 (1+ x))
        ((y z) (values 0 0) (values z x))))))
; ((SETQ X (1+ X)) (MULTIPLE-VALUE-SETQ (Y Z) (VALUES Z X))) 

'(print (caar '((y z) (values 0 0) (values z x))))

[on lisp] 11.1 ~ 11.4 Classic Macro (다시 정리)

(defun mappend (fn &rest lsts)
  "maps elements in list and finally appends all resulted lists."
  (apply #'append (apply #'mapcar fn lsts)))

(defmacro condlet (clauses &body body)
  (let ((bodfn (gensym))
        (vars (mapcar #'(lambda (v) (cons v (gensym)))
                      (remove-duplicates
                        (mapcar #'car 
                                (mappend #'cdr clauses))))))
    `(labels ((,bodfn ,(mapcar #'car vars)
                 ,@body))
       (cond ,@(mapcar #'(lambda (cl)
                           (condlet-clause vars cl bodfn))
                       clauses)))))

(defun condlet-clause (vars cl bodfn)
  `(,(car cl) (let ,(mapcar #'cdr vars)
                (let ,(condlet-binds vars cl)
                  (,bodfn ,@(mapcar #'cdr vars))))))


(defun condlet-binds (vars cl)
  (mapcar #'(lambda (bindform)
              (if (consp bindform)
                  (cons (cdr (assoc (car bindform) vars))
                        (cdr bindform))))
          (cdr cl)))
(print 
  (macroexpand-1 '(condlet (((= 1 2) (x (princ 'a)) (y (princ 'b)))
                          ((= 1 1) (y (princ 'c)) (x (princ 'd)))
                          (t       (x (princ 'e)) (z (princ 'f))))
                  (list x y z))))
(`((#:G3210 (Y X Z) (LIST X Y Z)))
 (COND
  ((= 1 2)
   (LET (#:G3211 #:G3212 #:G3213)
    (LET ((#:G3212 (PRINC 'A)) (#:G3211 (PRINC 'B)))
     (#:G3210 #:G3211 #:G3212 #:G3213))))
  ((= 1 1)
   (LET (#:G3211 #:G3212 #:G3213)
    (LET ((#:G3211 (PRINC 'C)) (#:G3212 (PRINC 'D)))
     (#:G3210 #:G3211 #:G3212 #:G3213))))
  (T
   (LET (#:G3211 #:G3212 #:G3213)
    (LET ((#:G3212 (PRINC 'E)) (#:G3213 (PRINC 'F)))
     (#:G3210 #:G3211 #:G3212 #:G3213)))))) 

(print 
  (macroexpand '(condlet (((= 1 2) (x 1) (y 1))
                          ((= 1 1) (y 2) (x 2)))
                  (list x y ))))

(LABELS ((#:G3210 (Y X) (LIST X Y))) ; condlet
 (COND ; condlet
  ((= 1 2) ; condlet-clause
   (LET (#:G3211 #:G3212) ; condlet-clause
    (LET ((#:G3212 1) (#:G3211 1)) ; condlet-clause, conlet-binds
      (#:G3210 #:G3211 #:G3212)))) ; condlet-clause
  ((= 1 1) ; condlet-clause
   (LET (#:G3211 #:G3212) ; condlet-clause
    (LET ((#:G3211 2) (#:G3212 2)) ; condlet-clause, condlet-binds
      (#:G3210 #:G3211 #:G3212)))))) l condlet-clause
condlet에서 vars는 각 심볼별로 gensym을 만든다. 심볼은 하나만 gensym으로 만들면 되니까 remove-duplicates으로 겹치는 것을 없앰
(setf my-val     
    (mapcar #'cdr
        ((lambda (clauses) 
            (mapcar #'(lambda (v) (cons v (gensym)))
                (remove-duplicates
                    (mapcar #'car
                            (mappend #'cdr clauses)))))
         '(((= 1 2) (x (princ 'a)) (y (princ 'b)))
           ((= 1 1) (y (princ 'c)) (x (princ 'd)))
           (t       (x (princ 'e)) (z (princ 'f)))))))
((Y . #:G3210) (X . #:G3211) (Z . #:G3212)) 
1. conlet : 필요한 심볼들에 (gensym)을 적용하고 (cond ...)의 형태를 만든다. ... 안에 넣을 형태를condlet-clause에게 만들도록 위임 (mapcar로 하나씩)
label을 붙이기 위해 bodfn에도 gensym을 만듬.

이제부터 중요한 것은 심볼대신 gensym 값들을 가지고 놀아서 매크로를 조작한다. 정말 꽤나 어렵다. 자세히보자.
바인딩이 끝났으면,label을 이용하여 함수를 하나 만든다. 바로 쓰지는 않고 이 label에 붙여진 (gensym)인 bodfn을 다음 함수에 던진다.
이 다음함수(condlet-clause)가 바인딩 후 람다를 실행하는 일까지 한다.

2. condlet-clause (조건절을 만들고 그 뒤에 필요한 심볼의 gensym을 LET에 넣고, 다음 LET에 선택된 심볼에 바인딩될 값을 condlet-binds에서 위임
조건절은 아래와 같다.
,(car cl)
필요한 심볼의 gensym값을 모두 넣는다.
(let ,(map #'cdr vars) 
   ...)
선택된 심볼에 들어갈 값을 conlet-binds가 하도록 한다.
; vars는 (x #:G1 y #:G2) 형태
; cl는 (조건절 바인딩) 형태
(let ,(condlet-binds vars cl) 
그 후 넘겨받은 bodfn(라벨함수)를 실행하고 그 매개변수로 바인딩된 심볼의 (gensym)을 넣는다.
(,bodfn ,@mapcar #'car vars)...

3. condlet-binds : 어떻게 바인딩 되는지 선택하고 그 형태를 만든다.
일단 mapcar를 이용하여 lambda를 실행하는 형태인데, (let ...) 안에 들어가는 리스트를 뱉으면 된다.
아래와 같은 형태다.
((#:G3211 2) (#:G3212 2))
먼저 assoc이 뭔지 보자
(setq values '((x . 100) (y . 200) (z . 50)))
(print (assoc 'y values))
(Y . 200) 

이제 조건절은 썼으니 필요없고 바인딩될 표현식만 필요하다.
mapcar의 두번째 매개변수로 이것들만 들어갈 것이다. 그리고 이것은 lambda에서 bindform이라는 이름으로 들어간다.
(cdr cl)
bindform으로 들어온 녀석이 cons인지(리스트인지) 확인한다. 대부분 cons일 것이나 아니면 nil을 뱉는것 같다.
#'(lambda (bindform)
    (if (consp bindform)
        ...
하이라이트다. 여기서는 bindform은 (x 100) 뭐 이런 형태로 이루어져 있을 것이다. 위 예제를 보자.
clauses에서 cl하나는 ((= 1 2) (x 1) (y 1)) 이걸 말한다.
여기서 (car cl)은 (= 1 2)
(cdr cl)은 ((x 1) (y 1))이다.
여기서 mapcar 에 람다로 들어오는 값은 각각 (x 1) 와 (y 1)가 따로 들어온다.
그렇다면 (car bindform)은 뭘까
(car '(x 1)) ; x 심볼을 가져옴
(assoc (car bindform) vars) ; 심볼을 가져와서 vars에 해당심볼(x)를 가진 리스트를 찾아낸다.
(cdr (assoc (car bindform) vars) ; (x #:G12) 형태를 받을 것인데 거기서 cdr로 (gensym)값을 해시맵처럼 가져온다.
(cons (cdr (assoc (car bindform) vars)) ; cons로 gensym값과 바인딩될 벨류를 연결하여 리스트를 만든다.
                  (cdr bindform)))) ; bindform은 (x 1)의 형태로 가져온다. (cdr bindform)은 1
; (#:G1 10) 뭐 이런 형태로 들어간다.
즉 mapcar의 개별 값을 알았으니 전체 값을 돌리면
'((#:G1 10) (#:G2 30))
이런 형태를 만들어질 것이다.

해석 끝

11.2 The with- Macro
두번째로 컨텍스트를 다루는 타입은 with-를 사용하는 매크로다. 넓은 시야로 보면, 컨텍스트는 하나의 세상 안에 있는 상태들이다. 이 상태들에는 특별한 변수들의 값, 자료구조의 내용물, 리스프 바깥 세상의 상태값들을 말한다.
이런 종류의 컨텍스트를 만들고자 한다면 매크로로 해야 한다. 그렇지 않으면 코드의 body들이 closure로 전부 감싸야 한다.
(with-open-file (s "dump" :direction :output)
  (princ 99 s))
표현식의 평가가 끝나면 "dump"파일은 저절로 닫힌다.
이런 연산자는 매크로로 정의되어야 한다. 왜냐하면 s를 바인딩 하기 때문이기도 하지만, 어짜피 새로운 컨텍스트에서 form(body)가 평가되어야 한다.
일반적으로 컨텍스트를 생성하는 매크로는 코드블록 안에다가 확장을 한 다음, 코드 앞 뒤로 할 일을 더한다.
일반적으로 코드가 body 뒤에 실행되면, 그 목적ㅇ느 시스템의 일관된 상태(실행이전 상태가 대부분일 듯)로 정리하는 것이다.
예를들어 with-open-file은 열어둔 파일을 닫아야 한다. 이런 경우, 일반적으로 unwind-protect로 context-creating매크로가 확장된다.

unwind-protect의 목적은 실행 중에 인터럽트가 발생해도 특정 표현식은 평가되도록 한다.
하나 이상의 매개변수를 받아서 순서대로 평가한다. 만약 모두 잘 진행되면 첫번째 매개변수를 리턴한다.
prog1처럼 prog1과의 차이는 error가 나더라도 나머지 매개변수가 평가되는 것이다.
(setq x 'a)
A

(unwind-protect
  (progn (princ "What error?")
         (error "This error."))
  (setq x 'b))
What error?
>> Error: This error.
자 setq가 실행되기 전에 에러를 던지게 했다.그런데 한번 x가 어떻게 되었나 보자.
x
B
with-open-file이 unwind-protect로 확장되기 때문에, 파일은 실행중에 에러가 나도 클로즈가 된다.

컨텍스트생성 매크로들은 대게 특정 앱을 위해 작성되어 진다. 예를 들어 다중원격 DB를 다루는 프로그램을 작성한다 하자.
프로그램은 한번에 한 DB랑 대화하며, 그 DB는 글로벌 *db*에 있다. DB에 연결하여 일을 하기 전에 락을 먼저 걸어야 한다. 그래야 다른 녀석이 동시에 사용할 수 없을 것이다.
만약 디비(db)에서 쿼리(q)에서 값을 원한다면, 아래처럼 짤 것이다.
(let ((temp *db*))
  (setq *db* db)
  (lock *db*)
  (prog1 (eval-query q)
         (release *db*)
         (setq *db* temp)))
매크로로 이 모든 장부를 숨길 수 있다. 아래 코드들을 보자.
; pure macro
(defmacro with-db (db &body body)
  (let ((temp (gensym)))
    `(let ((,temp *db*))
       (unwind-protect
         (progn
           (setq *db* ,db)
           (lock *db*)
           ,@body)
         (progn
           (release *db*)
           (setq *db* ,temp))))))

; Combination of macro and function:
(defmacro with-db (db &body body)
  (let ((gbod (gensym)))
    `(let ((,gbod #'(lambda () ,@body)))
       (declare (dynamic-extent ,gbod))
       (with-db-fn *db* ,db ,gbod))))

(defun with-db-fn (old-db new-db body)
  (unwind-protect
    (progn
      (setq *db* new-db)
      (lock *db*)
      (funcall body)) ; with-db-fn은 함수라서 일부러 람다로 보냄
    (progn
      (release *db*)
      (setq *db* old-db))))
복잡성이 올라가면 2번째 방식이 더 실용적이다.
CLTL2 COMMON LISP에서, dynamic-extent 선언은 효율적인 할당을 위해 body를 가지는 closure를 허용한다. (CLTL1은 안됨)
우리는 with-db-fn을 실행하는 동안만 closure가 필요하며, 이 선언으로 컴파일러가 스택에 이것을 위한 공간을 할당할 수 있도록 허용함.
이 공간은 가비지 컬렉터가 회수하지 않고 let식이 끝나면 자동으로 회수됨.

11.3 조건평가
조건절에 따라서 평가를 안하고 끝내는 수가 있다( 좋은 것)
(if t
    'a
    (/ x 0))
0으로 나누는 것은 에러이지만 'a를 뱉으면서 잘 동작하고 끝난다.
아래 if3는 if문에서 nil값도 따로 보는 것. if3에서 (nil)로 감싼 이유는 nil은 꽤나 모호한 의미를 가지기 때문이다.
; Fig 11.5 : Macros for conditional evaluation
(defmacro if3 (test t-case nil-case ?-case
  `(case ,test
     ((nil) ,nil-case)
     (?     ,?-case)
     (t     ,t-case)))

(defmacro nif (expr pos zero neg)
  (let ((g (gensym)))
    `(let ((,g ,expr))
       (cond ((plusp ,g) ,pos)
             ((zerop ,g) ,zero)
             (t ,neg)))))
nif(numeric if)는 숫자에 따라 리턴된다.
(mapcar #'(lambda (x) (nif x 'p 'z 'n))
        '(0 1 -1))
(Z P N)

그림 11.6은 조건평가를 이용한 다양한 매크로를 보여준다.
매크로 in은 효율적으로 값이 있는지 확인한다. 만약 in을 확장하면 아래처럼 보일 것이다.
(let ((x (foo)))
  (or (eql x (bar)) (eql x (baz))))

;; 하지만 member를 이용하면 아래처럼 이뻐찐다.
(member (foo) (list (bar) (baz)))
;; 하지만 효율적인가?
member는 두 측면에서 비효율적이다.
1 member가 검색하기 위해선 임시 리스트가 필요하다 (만드는 리소스)
2 이 임시 리스트를 만들기 위해 모든 리스트는 평가되어야 한다. 비록 몇 개는 필요 없어도 (필요없는 평가)
(foo)가 (bar)라면 (baz)는 평가할 필요가 없다.
우리는 이거 대신 좀 더 효율적인 추상화를 만들 수 있다. or을 이용하는 효율이면서 member와 같은 추상을 사용하는 매크로!
그것이 in 이다
(defmacro in (obj &rest choices)
  (let ((insym (gensym)))
    `(let ((,insym ,obj))
       (or ,@(mapcar #'(lambda (c) `(eql ,insym ,c))
                     choices)))))

(defmacro inq (obj &rest args)
  `(in ,obj ,@(mapcar #'(lambda (a) `',a)
                      args)))

(defmacro in-f (fn &rest choices)
  (let ((fnsym) (gensym)))
    `(let ((,fnsym ,fn))
       (or ,@(mapcar #'(lambda (c) `(funcall ,fnsym ,c))
                     choices)))))

(defmacro >case (expr &rest clauses)
  (let ((g (gensym)))
    `(let ((,g ,expr))
       (cond ,@(mapcar #'(lambda (cl) (>casex g cl))
                       clauses)))))

(defun >casex (g cl)
  (let ((key (car cl)) (rest (cdr cl)))
    (cond ((consp key) `((in ,g ,@key) ,@rest))
          ((inq key t otherwise) `(t ,@rest))
          (t (error "bad >case clause")))))
이제 in을 사용해보자
(in (foo) (bar) (baz))

(let ((#:g25 (foo)))
  (or (eql #:g25 (bar))
      (eql #:g25 (baz))))
inq(in queue)를 보자
(inq operator + - *)
;; expands into
(in operator '+ '- '*)

(member x (list a b) :test #'equal)
; can be duplicated by
(in-if #'(lambda (y) (equal x y)) a b)

(some #'oddp (list a b))
; becomes
(in-if #'oddp a b)

11.4Iteration 반복
때로 문제는 매개변수가 항상 평가되서가 아니다, 오히려 한번만 평가되는 경우가 문제인 경우가 있다.
표현식의 바디가 반복하려면 매크로 써야함
(defmacro forever (&body body)
  `(do ()
       (nil)
     ,@body))
단순한 반복예시로 일단 맛보자
; Fig 11.7 : Simple iteration macros.
(defmacro while (test &body body)
  `(do ()
       ((not ,test))
     ,@body))

(defmacro till (test &body body)
  `(do ()
       (,test)
     ,@body))

(defmacro for ((var start stop) &body body)
  (let ((gstop (gensym)))
    `(do ((,var ,start (1+ ,var))
          (,gstop ,stop))
         ((> ,var ,gstop))
       ,@body)))
이제 본편으로 들어가자. 그림 11.8에서 하나만 잘라서 봐보자
(defmacro do-tuples/o (params source &body body)
  (if parms
      (let ((src (gensym)))
        `(prog ((,src ,source))
           (mapc #'(lambda ,parms ,@body)
                 ,@(map0-n #'(lambda (n)
                               `(nthcdr ,n ,src))
                           (1- (length parms))))))))

(do-tuples/o (x y) '(a b c d)
  (princ (list x y)))
(A B)(B C)(C D)
NIL
이렇게 연결되는 것이다. 신기하다. 어덯게 펼쳐지는지 이야기 해보자.
; 
(macroexpand
  (do-tuples/o (x y) '(a b c d) (princ (list x y))))

(BLOCK NIL
 (LET ((#:G3215 '(A B C D)))
  (TAGBODY
   (MAPC #'(LAMBDA (X Y) (LIST X Y)) (NTHCDR 0 #:G3215) (NTHCDR 1 #:G3215)))))
TAGBOY는 라벨같은 거다. GOTO문처럼 이동을 하게 해주는 녀석인데 여기서는 안쓰는 듯하다. 생기기만 하고
NTHCDR은 첫번째 매개변수의 숫자만큼 까고 나머지 cdr 리스트를 리턴한다.

내가 단계단계 연필로 확장해본 걸 아래 적어놓겠다.
; 1
(if '(x y)
  (let ((src #:G1))
    `(prog ((#:G1 '(a b c d))
       (mapc #'(lambda (x y) (princ (list x y)))
             ,@(map0-n #'(lambda (n) '(nthcdr ,n ,src))
                       (1- (length parms)))))))
; parms 이 있는 경우
; src (gensym)으로 생성

; 2
(let ((src #:G1))
  `(prog ((#:G1 '(a b c d))
     (mapc #'(lambda (x y) (princ (list x y)))
           ,@('(a b c d) '(b c d)))...)

; 3
`(prog ((#:G1 '(a b c d))
   ...
   (princ (list a b))
   (princ (list b c))
   (princ (list c d))
  nil)
정말 매크로를 만드는 작업은 하나의 예술과도 같다.

[on lisp] 10 Other Macro Pitfalls

10.1 Number of Evaluations

; Fig 10.1 : Controlling argument evaluation.
; A correct version
(defmacro for ((var start stop) &body body)
  (let ((gstop (gensym)))
    `(do ((,var ,start (1+ ,var))
          (,gstop ,stop))
         ((> ,var ,gstop))
       ,@body)))

;; subject to multiple evaluations:
(defmacro for ((var start stop) &body body)
  `(do ((,var ,start (1+ ,var)))
       ((> ,var ,stop))
     ,@body))

;; Incorrect order of evaluation:
(defmacro for ((var start stop) &body body)
  (let ((gstop (gensym)))
    `(do ((,gstop ,stop)
          (,var ,start (1+ ,var)))
         ((> ,var ,gstop))
       ,@body)))
2번째 for문에는 버그가 있다.
; 무한에 가까운 출력
(let ((x 2))
  (for (i 1 (incf x))
    (princ i)))
stop 매개변수로 들어온 녀석이 각 루프를 돌 때마다 평가된다.
이 말은 (incf x)가 매번 평가되면서 끝나지 않는 것이다.
어떻게 다른애들은 괜찮고 얘는 괜찮지 않을까?
다른 애들은 어떻게 한번만 실행되고 안되는 것일까?
이 생각은 일단 접어두고

매개변수로 들어온 stop에 side-effect가 있다면, 결과값은 위처럼 문제가 생길 수 있다는 것이다.
for같은 매크로를 만들 때, 꼭 알아야 할 것이 있다. 매크로는 값을 받아서 실행하는 녀석이 아니다. 그럴거면 함수를 만들었을 것이다.
매크로는 표현식을 받는다. 그것도 자신의 리스트 자료구조로 말이다.
그리고 이 표현식이 확장시 있는 곳에 따라, 평가는 한번 이상 평가될 수 있다.

이 경우 값을 바인딩 해놓는 것이다. stop으로 들어오는 표현식을 미리 평가하여 값으로 받은 후 loop에는 해당 값이 들어가도록 하는 것이다.
위에 내용들 보면 gstop이 그런 역할을 하는 녀석.

매크로는 반복을 위한 녀석임이 확실하지 않으면, 매크로 호출에 나타나는 횟수만큼 정확하게 평가되도록 해야 한다.
이 경우가 적용되지 않는 명백한 사례가 있다: 커먼리습의 or에서 들어온 매개변수가 모든 평가되어야 한다면 꽤나 쓸모없는 녀석일 것이다.
그러나 그런 경우 유저는 얼마나 많은 평가가 일어날지 예상할 수 있는지 안다.
하지만 for의 두번째 경우는 그렇지 않다.
사용자는 stop표현식이 두번 이상 평가된다고 가정할 이유가 없으며, 실제로 평가할 이유도 없다. (그런 내용이 매크로를 호출할 때 보이지 않는다는 것이다)
바로 이 두번째 for문 매크로가 가장 실수를 많이하는 경우일 것이다. 단서가 보이지 않으니까.

setf를 이용한 매크로의 경우 Unintended multiple evaluation(의도치 않은 다중 평가)라는 아주 어려운 문제를 직면하는 경우가 많다.
커먼리습은 이런 매크로를 만드는 것이 쉽도록 여러가지 유틸리티를 제공한다. (챕터 12에서 보여줌)

10.2 Order of Evaluation(평가의 순서)
표현식 평가의 횟수만이 문제가 아니라, 평가의 순서가 문제를 일으킬 수 있다.
커먼리습 함수 호출에서, 매개변수는 왼쪽에서 오른쪽 순서로 평가된다.
(setq x 10) ; 10
(+ (setq x 3) x) ; 6
이렇게 다르다. 매크로도 동일한 일이 일어난다.
매크로는 일반적으로 매크로 호출을 할 때 보이는 순서와 동일한 순서로 내부적으로 평가되어야 한다.
아래 for문이 그 법칙을 무시한 녀석이다. 잘 보면 var이 첫번째 매개변수고 stop이 두번째 매개변수인데
뒤바뀌어 있다. 호출할 때는 이렇게 된다는 힌트가 전혀 없다. 다들 평가가 매개변수의 순서대로 될 것이라고 생각할 것이다.
(defmacro for ((var start stop) &body body)
  (let ((gstop (gensym)))
    `(do ((,gstop ,stop)
          (,var ,start (1+ ,var)))
         ((> ,var ,gstop))
       ,@body)))
하여 for문에는 작은 버그가 있는데, stop이 start보다 먼저 평가되는 것이다.
(let ((x 1))
  (for (i x (setq x 13))
    (princ i)))
13
NIL
보면 알겠지만, 123456789010111213 이렇게 보여야 하는데 (setq x 13)이 먼저 세팅되면서
루프가 돌 기회가 없게 된 것이다.

10.3 Non-functional Expanders
리스프에서는 매크로로 확장된 코드가 순수 함수적이길 바란다. 확장코드(expander code)는 매개변수말고는 영향을 받지 말아야 하며 리턴값 외에는 다른 영향을 주면 안된다.
컴파일된 코드의 매크로 호출은 런타임에 다시(또) 확장되지 않는다고 봐도 무방하다.
만약 그렇게 되지 않으면 커먼리습은 이 확장이라는 것이 언제, 어디서, 얼마나 자주 매크로 호출이 확장되는지 알 수 없게 된다.
이런 것들 중 하나로 매크로의 확장이 달라지면 에러로 간주한다. (상관없어야 한다)
아래를 보자.
(defmacro nil! (x)  ;;wrong
  (incf *nil!s*)
    `(setf ,x nil))
글로벌 값 *nil!s*가 정말 매크로가 호출될 때 1번씩 호출되서 카운팅 해줄까? 그렇게 생각하면 오산이다.
주어진 호출은 한번이상 확장될 수 있고, 종종 그렇게 된다.
소스코드를 변환을 수행할 프리프로세서는 일단 변환을 하기 전에 평가를 먼저 해서 변환을 할지 안할지를 결정한다.

다시 말하지만, 일반적으로 확장자코드는 인수 외의 것에 의존하면 안된다.
예를들어, 문자열에서 확장을 빌드하는 매크로가 있다하자.
이 매크로가 확장시 패키지가 무엇인지 그런건 가정하지 않도록 하자.
(defmacro string-call (opstring &rest args) ; wrong
  `(,(intern opstring) ,@args))

(defun our+ (x y) (+ x y))
OUR+
(string-call "OUR+" 2 3)
5
보면 intern을 이용하여 문자열을 가져와 연관된 심볼를 리턴한다. 하지만 우리가 optional package argument을 안쓰면(제거하면), 이건 현재 패키지에서 일어날 것이다.
따라서, 확장이 생성될 때 패키지에 의존한다. 해당 패키지에 our+가 표시되지 않으면 해당 내용은 nil을 리턴할 것이다.
이 말은 환경에 따라 값이 달라질 것이란 말임..

Miller and Benson's Lisp Style and Design에선 expander code안에 side-effect가 있는 것을 특별히 멍청한 예로 제시했다.

또 다른 문제가 있는데 그것은 &args에서 온다.
&rest 파라미터에 들어오는 값은 새로 만들어진 거라고 개런티할 수 없다.
결론만 먼저 말하면, &rest 파라미터로 오는 값을 파괴적으로 수정하면 안된다(값을 바꾸면 안대!)
이런 경우는 함수와 매크로 모두에게 해당하는 말이다. 함수로 쓸 때는 apply와 함께 쓸 때 문제가 나타난다.
(defun et-el (&rest args)
  (nconc args (list 'et 'al)))

(et-al 'smith 'jones)
; (SMITH JONES ET AL)
하지만 여기서 apply를 이용해서 호출하면 어떻게 될까. 이미 존재하는 자료구조를 바꾼다.
(setq greats '(l m))
(apply #'et-el greats)
; (l m et el)
greats
; (l m et el)

매크로에서는 문제가 더 심각해진다. 매크로에서 &rest를 변경한다면 매크로 호출을 변경하는 일이 생기게 된다.
이 말은 의도치 않게 매크로를 자기자신이 재작성하는 일이 벌어진다.
여기서 문제는 더 심각해진다. 이 일은 실제로 이미 존재하는 구현 위에서 일어난다.
만약 nconc를 &rest매개변수에 적용하는 매크로를 정의한다고 해보자.
(defmacro echo (&rest args)
  `',(nconc args (list 'amen))) ; `',(foo) is equivalent to `(quote ,(foo)).
그리고 이제 함수를 정의하고 호출한다.
(defun foo () (echo x))

(foo) ; (X AMEN AMEN)
(foo) ; (X AMEN AMEN AMEN)
뭐지 계속 foo가 바뀐다. 왜냐하면 각 매크로 확장이 foo의 정의를 변경하는 것이다.
바꿔보자.
(defmacro echo (&rest args)
  `'(,@args amen))
@(comman-at)은 append와 같기 때문에 파괴적이지 않다. 그러므로 안전하다.

그런데 이걸로 끝일까.
매크로에서는 &rest이것만 조심한다고 끝나는 것이 아니다.
아무 매크로 매개변수가 그렇게 될 수 있다. 그 녀석이 리스트라면
(defmacro crazy (expr) (nconc expr (list t)))

(defun foo () (crazy (list)))

(foo) ; (T T)

10.4 Recursion
함수를 재귀적으로 만드는 것은 자연스러운 일이다.
(defun our-length (x)
  (if (null x)
      0
      (1+ (our-length (cdr x)))))
(defun our-length (x)
  (do ((len 0 (1+ len))
       (y x (cdr y)))
      ((null y) len)))
첫번째는 재귀적이고 두번째는 아니다.
그런데 매크로에서는 그저 backquotes나 commas를 넣는다고 쉽게 재귀가 되지 않는다.
; Fig 10.2 : Mistaken analogy to a recursive function.
; this will work
(defun ntha (n lst)
  (if (= n 0)
      (car lst)
      (ntha (- n 1) (cdr lst))))

; this won't work
(defmacro nthb (n lst)
  `(if (= ,n 0)
       (car ,lst)
       (nthb (- ,n 1) (cdr ,lst))))
왜 nthb가 문제가 되는 걸까. 바로 확장할때마다 자신을 가지고 있기 때문인데
함수의 경우 확장을 하면 매개변수의 값이 평가가 되기 때문에 언제 멈출지 안다. 하지만 매크로는 형태만 있기 때문에 계속 무한 확장하는 수가 있다.
; (nthb x y) 를 확장하자.
(if (= x 0)
    (car y)
    (nthb (- x 1) (cdr y)))
; 위 녀석은 다시 확장된다.
(if (= x 0)
    (car y)
    (if (= (- x 1) 0)
        (car (cdr y))
        (nthb (- (- x 1) 1) (cdr (cdr y)))))
그럼 어떻게 해야 하나. 일단 재귀적으로 풀지 않으면 된다. 아래처럼
(defmacro nthc (n lst)
  `(do ((n2 ,n (1- n2))
        (lst2 ,lst (cdr lst2)))
       ((= n2 0) (car lst2))))

아래 다른 예시가 있다.
(defmacro nthd (n lst)
  `(nth-fn ,n ,lst))

(defun nth-fn (n lst)
  (if (= n 0)
      (car lst)
      (nth-fn (- n 1) (cdr lst))))

(defmacro nthe (n lst)
  `(labels ((nth-fn (n lst)
              (if (= n 0)
                  (car lst)
                  (nth-fn (- n 1) (cdr lst)))))
     (nth-fn ,n, lst)))
보면 알겠지만 재귀는 함수로 만들어서 그걸 사용하는 것이다.
혹은 labels를 이용하여 로컬 함수를 정의하여, 그 녀석을 실행하는 것이다.
결국 두 경우 모두 함수와 매크로를 더해서 실행하는 방식이다.
물론 매크로가 전부 확장 할 수는 없지만 어쨋든 재귀는 값을 평가하긴 해야 한다.
그러므로 재귀적으로 평가는 가능하게 만들 수는 있는 것이다.

매크로의 확장함수가 일반 리스프 함수이니까, 우리는 이걸로 재귀적으로 만들 수 있을 것이다.(그러니까 매크로의 확장이 매크로일 수 있겠냐 라는 것이다. 지금까지 안되었는데)
built-in or함수를 정의하면서 봐보자.
macro ora는 or-expand라는 재귀함수를 호출하여 확장을 생성한다.
orb도 같은 일을 한다. orb는 특이하게 값이 아니라 매크로에 대한 인수에서 반복한다 (??)
이렇게 되면 무한으로 펼쳐질 것 같지만, 하나의 매크로 확장단계에서 생성된 orb에 대한 호출은 다음 단계에서 let으로 대체된다(?) 하여 최종 확장에서는 let들의 중첩들에 지나지 않는다.
아래를 보자.
; (orb x y) 확장
(let ((g2 x))
  (if g2
      g2
      (let ((g3 y))
        (if g3 
            g3 
            nil))))
;; 여기서 마지막 nil이 있는데 거기서 재귀가 잘 멈춘것.
;; 아래는 코드
(defmacro ora (&rest args)
  (or-expand args))
(defun or-expand (args)
  (if (null args)
      nil
      (let ((sym (gensym)))
        `(let ((,sym ,(car args)))
           (if ,sym
               ,sym
               ,(or-expand (cdr args)))))))

(defmacro orb (&rest args)
  (if (null args)
      nil
      (let ((sym (gensym)))
        `(let ((,sym ,(car args)))
           (if ,sym
               ,sym
               (orb ,@(cdr args)))))))