2017년 1월 22일 일요일

[clojure][macro] 매크로사용하기 00

Code is Data 라는 말은 코드 자체가 프로그래밍 언어의 자료구조가 된다는 말이다. 클로저 매타프로그래밍(macro)를 하게 되면 우리는 expression level에서 생각하게 된다 rather than at the textual level.

코드 자체가 데이타(자료구조)이기 때문에 이것을 맘대로 바꿀 수 있다.

어쨋든 이게 뭐라는 거야
제대로 알아보자. (REPL을 예로들자)

REPL : Read-Eval-Print loop

Read : 문자단위표시방식 (character-based representation, typically an input stream) 을 받아서 클로저 자료구조로 변환한다. 그래서 Read phase(읽기상태)의 아웃풋은 data(데이타)이다. 이 데이터는 두번째 상태에서 평가된다. (데이터로!)

EVEL : 어떻게 EVAL에서 code-as-data에서 data-as-code로 변할까 (자료구조안에 있는 코드들이 코드로 만들어진 데이터)

read는 표현식(매크로가 잘 작동하는)을 작성 할 때(building the expression) 편한다. 다음의 인풋과 아웃풋을 보자. (어떻게 이게 동작하는지 잘보자.)

첫 매크로는 바로 머리에 때려박아주기 적절한 예시가 될것(ll give you enough to dive headfirst into your first macro.)

실제 클로저의 read 함수는 stream을 사용한다.(consume stream) 이런 행위는 셋업하는데 많은 짓을 해야 할지도 모른다.(verbose to set up.)

이 예제의 목적에 따라. read의 형제격인 read-string을 받겠다. (stream 대신 strings를 받는 것) 
 =================================================
(read-string "(+ 1 2 3 4 5)") 
;==> (+ 1 2 3 4 5)
(class (read-string "(+ 1 2 3 4 5)"))
;=> clojure.lang.PersistentList

 =================================================

여기 이 리스트가 우리가 지금까지 이야기 했던 데이터의 조각이다.
이건 클로저 리스트다. 그리고 아직까지는 클로저 코드다.  (Code as Data)
readstring에서 돌아온 값은 평가될 준비가 되어있다.
이 리스트를 평가할 때, 그 표현식의 값을 알 수 있을 것이라 기대한다.

=================================================
(eval (read-string "(+ 1 2 3 4 5)"))
;=> 15
(class (eval (read-string "(+ 1 2 3 4 5)")))
;=> java.lang.Long
(+ 1 2 3 4 5)
;=> 15
=================================================
이게 REPL에서 실제로 일어나고 있는 일이다. 아니면 완전체 클로저(full-blown clojure project)를 실행할때에도 실제 일어난다. 코드는 자료구조(data structure)로 읽어지고(be read into, 저장되고) 그러고 나서 평가된다(eval). 이 두 스텝(read, evaluate)을 놓고 보았을 때,
read와 eval 사이에 코드자신이 평가되기 위해 자기자신을 끼워넣는 것(꼽사리로 들어가는 것)을 어렵지 않을 것이다.


예를들어 we could replace addition with multiplication:
=================================================


(let [expression (read-string "(+ 1 2 3 4 5)")
  (cons (read-string "*")  ;; 꼽사리로 들어가는 현장
        (rest expression)))
;=> (* 1 2 3 4 5)

(eval *1) ;; *1 holds the result of the previous REPL evaluation
;=> 120
=================================================
이게 무슨 뜻이나면 read-string에다가 일단 곱하기(*)를 넣고 그 다음에는 더하기(+)를 제외한 값(rest)를 가져와서 합친것이다.

물론, 만약 새롱누 리스트를 평가(eval)한다면, 원래 결과와는 전혀 다른 내용이 나올 것이다. 이전에 나온 예시도 나쁘지 않지만 단순히 문자열값을 읽는 것보다는 제대로 표현식을 지어보자.
냅다 이런 "(+ 1 2 3 4 5)" 스트링을 만드는 것은 이상하단 말이야.
하지만 이런 형태의 리스트를 생성하고 싶다면 단순히 (+ 1 2 3 4 5) 이렇게 타입한다고 될 일이 아니다. 왜냐하면 이것 실제로 표현식으로 평가될 것이기 때문이다.
=================================================
(let [expression (+ 1 2 3 4 5)] ;; expression is bound to 15
  (cons
    (read-string "*") ;; *
      (rest expression))) ;; (rest 15)
; IllegalArgumentException Don't know how to create ISeq from: java.lang.Long
; clojure.lang.RT.seqFrom (RT.java:505)
=================================================
보아라 (+ 1 2 3 4 5)가 바로 그냥 15로 평가 되는 것이다. 왜냐하면 (실제로 표현식으로 평가되기 때문에!)

그래서 다른 솔루션이 필요하다. 하나는 실행을 억제하는 거다. 운좋게도 클로저에는 quote라는 동사(verb, 이 verb에 유념하라 function이 아니다)가 똑같은 일을 한다.
=================================================
(let [expression (quote (+ 1 2 3 4 5))]
  (cons (quote *)
        (rest expression)))
;=> (* 1 2 3 4 5)

(eval (let [expression (quote (+ 1 2 3 4 5))]
  (cons (quote *)
        (rest expression))))
;= 120
=================================================

quote verb는 사실 클로저의 특별한 형태(Clojure special form)이며 함수가 아니다.  우리는 이 verbs를 (공식적인 의미는 아님) 함수,매크로 그리고 special forms의 결합체라고 생각하면 된다.

리스트(Lists) 또한 verb 위치로 보여질 수 있는데 평가(eval)될 때는 함수위치로 축소되어야 한다.

함수(function)만이 아규먼트(여기서 아규먼트란 평가될 모~든 데이터를 말하는 듯 하다)를 제대로 평가한다. (컨트롤흐름을 코드(구현한 verbs)로 패스하기전에)
  * Only functions uniformly evaluate their arguments before passing control to the code that implements the verb.


=================================================
(defn print-with-asterisks [printable-argument]
  (print "*****")
  (print printable-argument)
  (println "*****"))

(print-with-asterisks "hi")
; *****hi*****
;= nil
=================================================
verb - 어떻게 정의하나? - 언제 아규먼트가 평가되나?
Functions - defn - Before executing the body
Macro -defmacro - Depends on macro; possibly multiple times or never.
Special form - We can’t! They’re only defined by the language. - Depends on special form; possibly multiple times or never.

자 위에 나온 print-with-asterisks같은 걸 만들 때는 굳이 언제 아규먼트가 평가(timing of the argument evaluation)되는지 알필요가 없다.

하지만 만약! 표현식을 아규먼트로 쓴다면! 그 해당 함수(print-with-asterisks)가 평가되기 전에 평가된다. (???)
 =================================================
(print-with-asterisks
  (do (println "in argument expression")
  "hi"))
=================================================
잘보라 사용하기 전에 이미 김이 다 빠져나간거다.
Macros and speical forms, 는 이 룰에서 자유롭다. 그리고 어떤 순서에서 정의한 거든 아규먼트를 평가할 것이다. (아니면 이상하게 작동해서 망하던가...)  이게 함수와 매크로의 큰 차이란다.

quote expression (quote ...)안에 있는 코드는 평가되지 않는다. 그리고 아주 단순한 토큰들도 가능하다. (심볼, 리스트, 벡터.. 모두)
=================================================
(quote 1)
;= 1
(quote "hello")
;= "hello"
(quote :kthx)
;= :kthx
(quote kthx)
;= kthx
=================================================
근데 귀찮게 항상 QUOTE을써야 하나? 아니다 더 좋은게 있다. 
Reader macro라는 것이다. 이건 우리가 만들 수 있는게 아니다. 언어딴에서 만들어진거라 한다.
=================================================

'(+ 1 2 3 4 5)
;= (+ 1 2 3 4 5)
'map
;= map
user.core=> map
#object[clojure.core$map 0x589a6fa "clojure.core$map@589a6fa"]
user.core= 'map
map
=================================================
오... 확실히 먼가 다르다.
=================================================
(let [expression '(+ 1 2 3 4 5)]
  (cons '* (rest expression)))
;= (* 1 2 3 4 5)
=================================================

댓글 없음 :

댓글 쓰기