2. - 프로그래밍 기법은 대부분 언어라는 틀 안
에서 이루어진다.
- 매크로를 작성 한다는 것은 언어 자체에
무엇인가를 추가한다는 의미이다.
3. 7.1 언제 매크로를 사용 해야 할까
매크로 클럽 규칙
첫번째 : 매크로를 사용하지 말라
두번째 : 매크로가 패턴을 추상화할 수 있는 유일한 방법일
때만 사용하라
예외 : 통일 기능을 하는 함수에 견줄 때 매크로를 작성하
는 것이 삶을 더 편하게 만든다면, 매크로를 작성해
도 좋다
4. 7.2 제어 구조 매크로
(if (= 1 1) (println "yep, math still works
today"))
=> yep, math still works today
5. 반대되는 unless를 만들어 봅시다
(defn unless [expr form]
(if expr nil form))
6. (unless false (println "this should print"))
=> this should print
(unless true (println "this should not print"))
=> this should not print
7. (defn unless [expr form]
(println "about to test...")
(if expr nil form))
(unless false (println "this sould print"))
=> this should print
=> about to test
(unless true (println "this sould not print"))
=> this should not print
=> about to test
8. 첫번째 : 매크로를 실행해 나온 결과를 매크
로를 호출한 자리에 치환하여 넣는다.
이 단계를 '매크로 익스팬션 타임' 이
라고 한다.
두번째 : 일반적인 '컴파일 타임' 단계에 들어
간다.
10. (unless false (println "this should print"))
(if false nil (println "this should print"))
11. (unless false (println "this should print"))
| this should print
Nil
(unless true (println "this should not print"))
=> Nil
12. 매크로의 전개
(defmacro unless [expr form]
(list 'if expr nil form))
- if 앞에 작은 따옴표를 붙임으로써, 매크로 익스팬션 타임에 if의 값
이 평가되지 않게 막는다.
- if를 평가하면 따옴표가 벗겨지고 if 자체가 컴파일 타임으로 넘겨진
다.
- expr과 form은 매크로 인자이기 때문에 따옴표를 붙이 지 않는다.
- 매크로 익스팬션 타임에 expr과 form 은 각각 그 해당 인자로 치환
된다.
- nil은 평가해도 nil이기 때문에 붙일 필요가 없다.
17. (when test & body)
(when-not test & body)
(defmacro when-not [test & body]
(list 'if test nil (cons 'do body)))
(macroexpand-1 '(when-not false
(print "1“)(print "2))))
=> (if false nil (do (print "1") (print "2")))
18. 7.3 더 쉽게 매크로 작성하기
매크로 호출 전개
(chain arm getHand) (. arm getHand)
(chain arm getHand, getFinger) (. (. arm getHand) getFinger)
( defmacro chain [x form]
(list '. x form) )
chain은 인자가 몇개건 모두 받을 수 있어야 한다. (재귀적 정의를 사용)
( defmacro chain
([x form] (list '. x form))
([x form & more] (concat (list 'chain (list '. x form)) more)) )
20. 구문 따옴표, 평가 기호, 이음 평가 기호
(defmacro chain [x form]
`(. ~x ~form))
(macroexpand '(chain arm getHand))
=> (. arm getHand)
( defmacro chain
([x form] `(. ~x ~form))
([x form & more] `(chain (. ~x ~form) ~more)) )
(macroexpand '(chain arm getHand getFinger))
=> (. (. arm getHand) (getFinger))
21. ( defmacro chain
([x form] `(. ~x ~form))
([x form & more] `(chain (. ~x ~form) ~@more)) )
(macroexpand '(chain arm getHand getFinger))
(. (. arm getHand) getFinger)
1. 템플릿으로 다루기 위해 구문따옴표 (`)를 시작한다.
2. 각인자 앞에는 (~)를 붙여 준다3. 여러 인자를 삽입하기 위해
서는 (~@)를 사용한다.
22. 매크로 안에서 이름 만들기
(time (str "a" "b"))
=>"Elapsed time: 0.06 msecs“
(let [start (System/nanoTime)
result (str "a" "b")]
{:result result :elapsed
(- (System/nanoTime) start)})
=> {:elapsed 61000, :result “ab”}
26. 7.4 매크로의 분류
1. 매크로를 작성하지 마라
2. 매크로가 패턴을 추상화할 수 있는 유일
한 벙법일 때만 사용하라
3. 동일 기능을 하는 함수에 견줄때 매크로
를 작성하는 것이 삶을 더 편하게 만든다
면 매크로를 작성해도 좋다.
27. 매크로 사용이 옳은 이유 매크로의 용도 예
특수 구문 조건부 평가 when, when-not, and, or, comment
특수 구문 var를 생성하기 위해 defn, defmacro, defmulti, defstruct,
declare
특수 구문 자바와 상호작용하기 위해 .., doto, import-static
호출 시 편의 평가를 지연하기 위해 Lazy-cat, lazy-seq, delay
호출 시 편의 구문들을 감싸기 위해 With-open, dosync, with-out-str, time,
assert
호출 시 편의 불필요한 lambda를 피하기
위해
With-open, dosync, with-out-sr, time,
assert
41. 구문 감싸기
( with-out-str & exprs )
(with-out-str (print "hello, ") (print "world"))
-> "Hello, World"
(defmacro with-out-str
[& body]
`(let [s# (new java.io.stringWriter)] (1)
(binding [*out* s#] (1)
~@body (2)
(str s#)))) (3)
1. 설정 : 구문을 평가하기 앞서서 특정한 환경을 만들어 낸다. let이나 binding을 통해 새로운
바인딩을 만든다. (1)
2. 평가 : 인자로 받은 구문을 평가한다. 평가 해야할 구문이 보통 여러 개이기 때문에, 이름 평
가 기호(~@)를 붙이게 된다. (2)
3. 해제 : 생성 햇던 환경을 원래대로 되돌리고, 적절한 값을 반환한다. (3)
Let은 시작 시간을 start에 바인딩한후, 구문을 실행해 그 결과를 result에 바인딩 한다. 그리고 start로 부터
지난 시간 및 result로 이루어진 맵을 반환한다.
타임과 유사 하지만 bench라는 매크로를 만들어 보자
문제 발생
심벌 캡쳐라는 이름 충돌이 발생
전개하면 구문 따옴표 안의 심벌은 이름 공간이 명시된 이름으로 바뀌게 된다.
우리는 지역에 한정된 이름을 만들기 원하는데, 심벌 캡쳐라고 부르는 매크로 버그를 만들지 않도록 도와준다.
제대로 잘 나온다… 하지만 언제나 행운이 계속 되진 않는다.
!!코드 테스트에는 이렇게 나오지 않았음
bench는 start라는 심벌에 (System/nanoTime)의 값을 바인딩하게 된다. 문제 발생
그러므로 매크로를 호출하는 쪽에서 사용할 어떤 이름과도 겹치지 않는 '유일한' 이름을 만들어 낸다면 문제는 해결된다.
구문따옴표 안에서 어떤 이름 뒤에 #을 붙이면 그 이름 뒤에 언더 스코어(_)와 고유한 ID를 붙인 새로운 심벌을 자동으로 생성해준다.
클로져와 clojure-contrib에 있는 매크로를 살펴본 결과 대부분이 매크로 클럽의 규칙을 따르고 있었다.
1. 매크로를 작성하지 마라
2. 매크로가 패턴을 추상화할 수 있는 유일한 벙법일 때만 사용하라
3. 동일 기능을 하는 함수에 견줄때 매크로를 작성하는 것이 삶을 더 편하게 만든다면 매크로를 작성해도 좋다.
and는 재귀적으로 적용이 된다
인자가 없는 경우와 하나인 경우가 기저가 된다.
인자가 없는 경우 true를 반환한다.
인자가 하나 이상인 경우에는 그 인자를 반환한다.
인자가 두 개 이상 들어올 경우에는 첫 번째 인자를 조건 식으로 삼아, (1)에서 평가한다.
평가 결과가 참이면, and를 재귀적으로 불러 나머지 원소에 대한 평가를 계속 한다. (2)
And는 false를 만나면 연산을 멈추고
Or은 true를 만나면 연산을 멈춘다.
이렇게 불필요한 평가를 절약하는 방식의 최고는 comment매크로다
comment 매크로는 인자를 '전혀' 평가하지 않으며, 때때로 파일의 마지막에서 API사용법을 보이는 데 이용된다.
- 클로저에는 구조체를 생성하는 create-struct라는 함수가 있다.
- 깔끔하지가 않다.
- defstruct는 간단한 매크로로, 이미 클로저에 포함되어 있다.
- 굉장히 간단하기 때문에 함수로 만들수 있겠다고 생각하겠지만 할수 없다. 왜냐하면 def는 특수 구문이기 때문이다.
def는 매크로 타임에 생성 되어야 하고, 런타임에 동적으로 def를 호출할수는 없다.
이렇게 하는건 공간 낭비 임으로
클로저로 declare를 구현한 코드다
스튜어트 시에라가 이과정을 자동화할수 있는 import-static 매크로를 만들었다.
import-static은 자바 클래스의 정적 멤버를 지역 이름 공간에서 간단한 이름으로 사용할 수 있도록 해준다.
4.3절 '지연 스퀀스와 무하한 시퀀스'에서 이런 매크로를 본적이 있는데 바로 lazy-seq다. 다른예로 delay가 있다.
(delay에 exprs를 넘기면, 그저 exprs를 저장해 놓고 이 값을 계산하도록 강요받을 때까지 아무것도 하지 않는다.
sleep을 사용해서 굉장히 긴 연산을 흉내 내는 구문을 delay에 넘겨 보자
Delay를 통해 그 평가가 지연된 구문을 샐행하려면 force를 사용한다.
force를 처음 적용하면 지연된 구문을 실행한 다음 그 결과를 캐시하고 다시 force를 적용하면 캐시된 결과를 바로 반환한다.
클로져의 지연 평가를 구현하는 매크로는 모두 clojure.jar안의 자바 코드를 호출하고 있다.
구문이 실행되기 전이나 후에 특정한 행동을 추가하고 싶다면, 구문을 감싸는 매크로를 이용한다.
Time은 타이머를 시작시킨뒤, 구문들을 평가하고 실행시간을 보고 한다.
Let과 binding은 바인딩을 수립한 뒤, 구문들을 평가하고, 수립한 바인딩을 해제 한다.
등등
with-out-str는 임시로 *out*을 새로운 StringWriter에 다인딩한후 주어진 exprs을 평가하고 결과로 나온 문자열을 *out*으로 출력한다.
익명함수를 lamda라고 한다.
때로는 lambda를 인자로 받는 함수가 매크로를 대신할 수 있다.
익명함수를 사용할 때에는 호출하는 쪽에서 구문을 lambda로 감싸야 하기 때문에, 일이 더 많아 진다.
Bench-fn이 bench의 완벽한 대채물이 아니기 때문에 매크로를 사용한다