7장매크로
-
Upload
park-min-wook -
Category
Education
-
view
1.083 -
download
1
Transcript of 7장매크로
7 장 . 매크로
아꿈사 박민욱
- 프로그래밍 기법은 대부분 언어라는 틀 안에서 이루어진다 .
- 매크로를 작성 한다는 것은 언어 자체에 무엇인가를 추가한다는 의미이다 .
7.1 언제 매크로를 사용 해야 할까매크로 클럽 규칙
첫번째 : 매크로를 사용하지 말라두번째 : 매크로가 패턴을 추상화할 수 있는 유일한 방법일
때만 사용하라예외 : 통일 기능을 하는 함수에 견줄 때 매크로를 작성하 는
것이 삶을 더 편하게 만든다면 , 매크로를 작성해 도 좋다
7.2 제어 구조 매크로(if (= 1 1) (println "yep, math still
works today"))=> yep, math still works today
반대되는 unless 를 만들어 봅시다
(defn unless [expr form](if expr nil form))
(unless false (println "this should print"))
=> this should print
(unless true (println "this should not print"))
=> this should not print
(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
첫번째 : 매크로를 실행해 나온 결과를 매크로를 호출한 자리에 치환하여 넣는다 . 이 단계를 ' 매크로 익스팬션 타임 ' 이 라고 한다 .
두번째 : 일반적인 ' 컴파일 타임 ' 단계에 들어간다 .
(unless expr form) -> (if expr nil form)
(defmacro name doc-string? attr-map? [params] body)
(defmacro unless [expr form](list 'if expr nil form))
(unless false (println "this should print"))
(if false nil (println "this should print"))
(unless false (println "this should print"))
| this should printÞ Nil
(unless true (println "this should not print"))
=> Nil
매크로의 전개(defmacro unless [expr form]
(list 'if expr nil form))
- if 앞에 작은 따옴표를 붙임으로써 , 매크로 익스팬션 타임에 if의 값이 평가되지 않게 막는다 .
- if를 평가하면 따옴표가 벗겨지고 if 자체가 컴파일 타임으로 넘겨진다 .
- expr과 form은 매크로 인자이기 때문에 따옴표를 붙이 지 않는다 .
- 매크로 익스팬션 타임에 expr과 form 은 각각 그 해당 인자로 치환된다 .
- nil은 평가해도 nil이기 때문에 붙일 필요가 없다 .
(macroexpand-1 form)
(macroexpand-1 '(unless flase (println "this should print")))
=> (if false nil (println "this should print"))
(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"))
(macroexpand-1 '(.. arm getHand getFin-ger))
=> (.. (. arm getHand) getFinger)
(macroexpand form)
(macroexpand '(.. arm getHand getFinger))
=> (. (. arm getHand) getFinger)
when 과 when-not
(unless false (println "this") (println "and also this"))
=> 에러
(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")))
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)) )
(concat (list 'chain (list '. x form)) more)))
(defmacro chain([x form] (. ${x} ${form}))([x form & more] (chain (. ${x} ${form}) ${more})))
구문 따옴표 , 평가 기호 , 이음 평가 기호
(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))
( defmacro chain([x form] `(. ~x ~form))([x form & more] `(chain (. ~x ~form) ~@more)) )
(macroexpand '(chain arm getHand getFinger))Þ (. (. arm getHand) getFinger)
1. 템플릿으로 다루기 위해 구문따옴표 (`) 를 시작한다 .2. 각인자 앞에는 (~) 를 붙여 준다 3. 여러 인자를 삽입하기 위해서는
(~@) 를 사용한다 .
매크로 안에서 이름 만들기(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”}
( 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})
(let [a 1 b 2](bench (+ a b)))
=> {:result 3, :elapsed 39000}
(let [start 1 end 2](bench (+ start end)))
=> {: result 123536234634636364, :elapsed 39000}
(defmacro bench [expr]'(let [start# (System/nanoTime)result# ~expr]{:result result# :elapsed (- (System/nanoTime) start#)}))
(bench (str "a" "b")){:elapsed 63000, :result "ab"}
7.4 매크로의 분류1. 매크로를 작성하지 마라2. 매크로가 패턴을 추상화할 수 있는 유일한
벙법일 때만 사용하라3. 동일 기능을 하는 함수에 견줄때 매크로를
작성하는 것이 삶을 더 편하게 만든다면 매크로를 작성해도 좋다 .
매크로 사용이 옳은 이유 매크로의 용도 예
특수 구문
조건부 평가 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
조건부 평가
(defmacro and([] true)([x] x)([x & rest]
`(let [and# ~x](1) (if and# (and ~@rest) and#))))(2)
(and 1 0 nil false)-> nil
(or 1 0 nil false)-> true
(comment & exprs)
var 의 생성클로저의 var 는 def 특수 구문을 이용해
만들어 진다
defn, demacro, defmulti 등은 모두 결국 def 를 호출하는 매크로다 .
(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)))
(def a)(def b)(def c)(def d)
(declare a b c d)
(defmacro declare [& names] `(do ~@(map #(list 'def %) names)))
하나씩 분석해 보자익명함수 #(list 'def %) 가 개개의 def
구문을 만드는 역할을 한다 .
(#(list 'def %) 'a)-> (def a)
map 은 names 안의 심벌에 대해서 이 익명 함수는 적용한다 .
(map #(list 'def %) '[a b c d])-> ((def a) (def b) (def c) (def d))
결국 do 가 이 같은 표현들을 묶어서 , 문법적으로 올바른 클로저 구문이 되게 한다 .
'(do ~@(map #(list 'def %) '[a b c d]))-> (do (def a) (def b) (def c) (def d))
이 코드는 declare 매크로를 macroex-pand-1 으로 전개한 결과와 동일하다 .
(macroexpand-1 '(declare a b c d))-> (do (def a) (def b) (def c) (def d))
자바와의 상호작용Math/PI-> 3.14159265389793
(Math/pow 10 3)-> 1000.0
Math 가 걸리적거림으로 일일이 var 들을 선언 할 수는 있다 .
(def PI Math/PI)(def pow [b e] (Math/pow b e))
(use '[clojure.contrib.import-static :only (import-static)])
(import-static java.lang.Math PI pow)
PI-> 3.14159265389793
(pow 10 3)-> 1000.0
평가지연(delay & exprs)(def slow-clac (delay (Thread/sleep 5000) "done!"))
(force x)slow-calc 에 force 를 반복해서 적용해보자
(force slow-calc) -> "done!“ (5 초후 )
(force slow-calc)-> "done!“ ( 바로 )
구문 감싸기( 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)
(assert expr)논리적으로 참이 아니면 예외를 발생(assert (= 1 1))=> Nil
(assert (= 1 2))=> java.lang.Exception: Assert failed:
(= 1 2)
불필요한 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}