7장매크로

43
7 장 . 장장장 장장장 장장장

Transcript of 7장매크로

Page 1: 7장매크로

7 장 . 매크로

아꿈사 박민욱

Page 2: 7장매크로

- 프로그래밍 기법은 대부분 언어라는 틀 안에서 이루어진다 .

- 매크로를 작성 한다는 것은 언어 자체에 무엇인가를 추가한다는 의미이다 .

Page 3: 7장매크로

7.1 언제 매크로를 사용 해야 할까매크로 클럽 규칙

첫번째 : 매크로를 사용하지 말라두번째 : 매크로가 패턴을 추상화할 수 있는 유일한 방법일

때만 사용하라예외 : 통일 기능을 하는 함수에 견줄 때 매크로를 작성하 는

것이 삶을 더 편하게 만든다면 , 매크로를 작성해 도 좋다

Page 4: 7장매크로

7.2 제어 구조 매크로(if (= 1 1) (println "yep, math still

works today"))=> yep, math still works today

Page 5: 7장매크로

반대되는 unless 를 만들어 봅시다

(defn unless [expr form](if expr nil form))

Page 6: 7장매크로

(unless false (println "this should print"))

=> this should print

(unless true (println "this should not print"))

=> this should not print

Page 7: 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

Page 8: 7장매크로

첫번째 : 매크로를 실행해 나온 결과를 매크로를 호출한 자리에 치환하여 넣는다 . 이 단계를 ' 매크로 익스팬션 타임 ' 이 라고 한다 .

두번째 : 일반적인 ' 컴파일 타임 ' 단계에 들어간다 .

Page 9: 7장매크로

(unless expr form) -> (if expr nil form)

(defmacro name doc-string? attr-map? [params] body)

(defmacro unless [expr form](list 'if expr nil form))

Page 10: 7장매크로

(unless false (println "this should print"))

(if false nil (println "this should print"))

Page 11: 7장매크로

(unless false (println "this should print"))

| this should printÞ Nil

(unless true (println "this should not print"))

=> Nil

Page 12: 7장매크로

매크로의 전개(defmacro unless [expr form]

(list 'if expr nil form))

- if 앞에 작은 따옴표를 붙임으로써 , 매크로 익스팬션 타임에 if의 값이 평가되지 않게 막는다 .

- if를 평가하면 따옴표가 벗겨지고 if 자체가 컴파일 타임으로 넘겨진다 .

- expr과 form은 매크로 인자이기 때문에 따옴표를 붙이 지 않는다 .

- 매크로 익스팬션 타임에 expr과 form 은 각각 그 해당 인자로 치환된다 .

- nil은 평가해도 nil이기 때문에 붙일 필요가 없다 .

Page 13: 7장매크로

(macroexpand-1 form)

(macroexpand-1 '(unless flase (println "this should print")))

=> (if false nil (println "this should print"))

Page 14: 7장매크로

(defmacro bad-unless [expr form] (list 'if 'expr nil form))

(macroexpand-1 '(bad-unless false (println "this should print")))

=> (if expr nil (println "this should print"))

Page 15: 7장매크로

(macroexpand-1 '(.. arm getHand getFin-ger))

=> (.. (. arm getHand) getFinger)

(macroexpand form)

(macroexpand '(.. arm getHand getFinger))

=> (. (. arm getHand) getFinger)

Page 16: 7장매크로

when 과 when-not

(unless false (println "this") (println "and also this"))

=> 에러

Page 17: 7장매크로

(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")))

Page 18: 7장매크로

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)) )

Page 19: 7장매크로

(concat (list 'chain (list '. x form)) more)))

(defmacro chain([x form] (. ${x} ${form}))([x form & more] (chain (. ${x} ${form}) ${more})))

Page 20: 7장매크로

구문 따옴표 , 평가 기호 , 이음 평가 기호

(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))

Page 21: 7장매크로

( defmacro chain([x form] `(. ~x ~form))([x form & more] `(chain (. ~x ~form) ~@more)) )

(macroexpand '(chain arm getHand getFinger))Þ (. (. arm getHand) getFinger)

1. 템플릿으로 다루기 위해 구문따옴표 (`) 를 시작한다 .2. 각인자 앞에는 (~) 를 붙여 준다 3. 여러 인자를 삽입하기 위해서는

(~@) 를 사용한다 .

Page 22: 7장매크로

매크로 안에서 이름 만들기(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”}

Page 23: 7장매크로

( defmacro bench [expr]`(let [start (System/nanoTime)result ~expr]{:result result :elapsed (- (system/nanoTime) start)}) )

(bench (str "a" "b"))Þ Err

(macroexpand-1 '(bench (str "a" "b")))Þ (clojure.core/let [examples.macros/start (System/nanoTime)

Examples.macros/result (str “a” “b”)]{:elapsed (clojure.core/- (System/nanoTime) examples.macros/start):resultexamples.macros/result})

Page 24: 7장매크로

(let [a 1 b 2](bench (+ a b)))

=> {:result 3, :elapsed 39000}

(let [start 1 end 2](bench (+ start end)))

=> {: result 123536234634636364, :elapsed 39000}

Page 25: 7장매크로

(defmacro bench [expr]'(let [start# (System/nanoTime)result# ~expr]{:result result# :elapsed (- (System/nanoTime) start#)}))

(bench (str "a" "b")){:elapsed 63000, :result "ab"}

Page 26: 7장매크로

7.4 매크로의 분류1. 매크로를 작성하지 마라2. 매크로가 패턴을 추상화할 수 있는 유일한

벙법일 때만 사용하라3. 동일 기능을 하는 함수에 견줄때 매크로를

작성하는 것이 삶을 더 편하게 만든다면 매크로를 작성해도 좋다 .

Page 27: 7장매크로

매크로 사용이 옳은 이유 매크로의 용도 예

특수 구문

조건부 평가 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

Page 28: 7장매크로

조건부 평가

(defmacro and([] true)([x] x)([x & rest]

`(let [and# ~x](1) (if and# (and ~@rest) and#))))(2)

Page 29: 7장매크로

(and 1 0 nil false)-> nil

(or 1 0 nil false)-> true

(comment & exprs)

Page 30: 7장매크로

var 의 생성클로저의 var 는 def 특수 구문을 이용해

만들어 진다

defn, demacro, defmulti 등은 모두 결국 def 를 호출하는 매크로다 .

Page 31: 7장매크로

(create-struct & key-symbols)

(def person (create-struct :first-name :last-name))

(defstruct name & key-symbols)

(defmacro defstruct[name & keys]`(def ~name (create-struct ~@keys)))

Page 32: 7장매크로

(def a)(def b)(def c)(def d)

(declare a b c d)

Page 33: 7장매크로

(defmacro declare [& names] `(do ~@(map #(list 'def %) names)))

Page 34: 7장매크로

하나씩 분석해 보자익명함수 #(list 'def %) 가 개개의 def

구문을 만드는 역할을 한다 .

(#(list 'def %) 'a)-> (def a)

Page 35: 7장매크로

map 은 names 안의 심벌에 대해서 이 익명 함수는 적용한다 .

(map #(list 'def %) '[a b c d])-> ((def a) (def b) (def c) (def d))

Page 36: 7장매크로

결국 do 가 이 같은 표현들을 묶어서 , 문법적으로 올바른 클로저 구문이 되게 한다 .

'(do ~@(map #(list 'def %) '[a b c d]))-> (do (def a) (def b) (def c) (def d))

Page 37: 7장매크로

이 코드는 declare 매크로를 macroex-pand-1 으로 전개한 결과와 동일하다 .

(macroexpand-1 '(declare a b c d))-> (do (def a) (def b) (def c) (def d))

Page 38: 7장매크로

자바와의 상호작용Math/PI-> 3.14159265389793

(Math/pow 10 3)-> 1000.0

Math 가 걸리적거림으로 일일이 var 들을 선언 할 수는 있다 .

(def PI Math/PI)(def pow [b e] (Math/pow b e))

Page 39: 7장매크로

(use '[clojure.contrib.import-static :only (import-static)])

(import-static java.lang.Math PI pow)

PI-> 3.14159265389793

(pow 10 3)-> 1000.0

Page 40: 7장매크로

평가지연(delay & exprs)(def slow-clac (delay (Thread/sleep 5000) "done!"))

(force x)slow-calc 에 force 를 반복해서 적용해보자

(force slow-calc) -> "done!“ (5 초후 )

(force slow-calc)-> "done!“ ( 바로 )

Page 41: 7장매크로

구문 감싸기( 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)

Page 42: 7장매크로

(assert expr)논리적으로 참이 아니면 예외를 발생(assert (= 1 1))=> Nil

(assert (= 1 2))=> java.lang.Exception: Assert failed:

(= 1 2)

Page 43: 7장매크로

불필요한 lambda 피하기

7.3 절 bench(defn bench-fn [f]

(let [start (system/nanoTime)result (f)]

{:result result :elapsed (- (System/nanoTime) start)}))

(bench (+ 1 2))=> {:elapsed 44000, :result 3}

(bench-fn (fn [] (+ 1 2)))=> {:elasped 53000, :result 3}