Skip to content

Instantly share code, notes, and snippets.

@philoskim
Last active December 24, 2018 02:49
Show Gist options
  • Save philoskim/a2aa776ca78d53db30f141cb08c07649 to your computer and use it in GitHub Desktop.
Save philoskim/a2aa776ca78d53db30f141cb08c07649 to your computer and use it in GitHub Desktop.
Clojure Transducer Test

Clojure Transducer Test

transducer가 어떤 방식으로 작동하는지 알기 위해 테스트 코드를 작성했습니다. 아래의 my-filter, my-map, my-conj는 각각 clojure.corefilter, map, conj 함수의 내용 일부를 이 테스트를 위해 약간 수정한 것입니다. 코드가 약간 길기는 합니다만, 찬찬히 읽어 보면 이해하기 어려운 코드는 아닙니다.

Tip
참고로, 아래에서 outer-fn-in-으로 시작하는 함수는 transducer이고, inner-fn-in-으로 시작하는 함수는 reducing function입니다. Clojure에서는 무명 함수에도 이와같이 이름을 붙일 수 있습니다. 아래처럼 디버깅할 때 요긴합니다.
(ns clj-test.transducer)

(defn my-filter
  [pred]
  (fn outer-fn-in-filter [rf]
    (fn inner-fn-in-filter
      ([]
       (let [r (rf)]
         (println "rf-in-filter =" rf)
         (println "my-filter [] post: result =" r)
         r))
      ([result]
       (println "rf-in-filter = " rf)
       (println "my-filter [result] pre: result =" result)
       (let [r (rf result)]
         (println "my-filter [result] post: result =" r)
         r))
      ([result input]
       (println "rf-in-filter = " rf)
       (println "my-filter [result input] pre: result =" result ", input =" input)
       (let [r (if (pred input)
                 (rf result input)
                 result)]
         (println "my-filter [result input] post: result =" r)
         r)))))

(defn my-map
  [f]
  (fn outer-fn-in-map [rf]
    (fn inner-fn-in-map
      ([]
       (println "rf-in-map =" rf)
       (let [r (rf)]
         (println "my-map [] post: result =" r)
         r))
      ([result]
       (println "rf-in-map = " rf)
       (println "my-map [result] pre: result =" result)
       (let [r (rf result)]
         (println "my-map [result] post: result =" r)
         r))
      ([result input]
       (println "rf-in-map = " rf)
       (println "my-map [result input] pre: result =" result ", input =" input)
       (let [r (rf result (f input))]
         (println "my-map [result input] post: result =" r)
         r)))))

(defn my-conj
  ([]
   (println "my-conj []: result =" [])  ;; (1)
   [])
  ([result]
   (println "my-conj [result]: result =" result)
   result)
  ([result input]
   (println "my-conj [result input] pre: result =" result ", input =" input)
   (let [r (. clojure.lang.RT (conj result input))]
     (println "my-conj [result input] post: retrun =" r)
     r) ))

(def xform (comp (my-filter odd?) (my-map #(* % 10))))

(transduce xform my-conj [1 2 3 4 5])

다음은 위 함수를 실행한 결과입니다. 화면 출력 결과는 보기 좋도록 일부 편집했습니다.

;;;; transduce의 함수 인자에 초기값이 지정되어 있지 않아, 위의 (1) 부분이 실행되었다.
;    my-conj []: result = []

;;;; <<<< 이 부분이 가장 중요 >>>>
;;;; 아래의 rf-in-filter 함수는 최종적으로 inner-fn-in-map 함수를 호출하므로,
;;;; transducer가 아니라 reducing function이다. 다시 말해, comp 함수의 함수 합성을
;;;; 통해 받게 되는 인자 rf는 transducer가 아니라 reducing function이다.
;    rf-in-filter = #function[clj-test.transducer/my-map/outer-fn-in-map--6819/inner-fn-in-map--6820]
;    my-filter [result input] pre: result = [] , input = 1
;      rf-in-map =  #function[clj-test.transducer/my-conj]
;      my-map [result input] pre: result = [] , input = 1
;        my-conj [result input] pre: result = [] , input = 10
;        my-conj [result input] post: retrun = [10]
;      my-map [result input] post: result = [10]
;    my-filter [result input] post: result = [10]

;    rf-in-filter = #function[clj-test.transducer/my-map/outer-fn-in-map--6819/inner-fn-in-map--6820]
;    my-filter [result input] pre: result = [10] , input = 2
;    my-filter [result input] post: result = [10]

;    rf-in-filter = #function[clj-test.transducer/my-map/outer-fn-in-map--6819/inner-fn-in-map--6820]
;    my-filter [result input] pre: result = [10] , input = 3
;      rf-in-map = #function[clj-test.transducer/my-conj]
;      my-map [result input] pre: result = [10] , input = 3
;        my-conj [result input] pre: result = [10] , input = 30
;        my-conj [result input] post: retrun = [10 30]
;      my-map [result input] post: result = [10 30]
;    my-filter [result input] post: result = [10 30]

;    rf-in-filter = #function[clj-test.transducer/my-map/outer-fn-in-map--6819/inner-fn-in-map--6820]
;    my-filter [result input] pre: result = [10 30] , input = 4
;    my-filter [result input] post: result = [10 30]

;    rf-in-filter = #function[clj-test.transducer/my-map/outer-fn-in-map--6819/inner-fn-in-map--6820]
;    my-filter [result input] pre: result = [10 30] , input = 5
;      rf-in-map = #function[clj-test.transducer/my-conj]
;      my-map [result input] pre: result = [10 30] , input = 5
;        my-conj [result input] pre: result = [10 30] , input = 50
;        my-conj [result input] post: retrun = [10 30 50]
;      my-map [result input] post: result = [10 30 50]
;    my-filter [result input] post: result = [10 30 50]

;;;; 아래의 실행 결과에서 최종 결과값을 반환할 때, reducing funcion에 제공된 함수들 중에서,
;;;; 인자 하나짜리 함수를 호출하는 것을 확인할 수 있다.
;    rf-in-filter = #function[clj-test.transducer/my-map/outer-fn-in-map--6819/inner-fn-in-map--6820]
;    my-filter [result] pre: result = [10 30 50]
;      rf-in-map = #function[clj-test.transducer/my-conj]
;      my-map [result] pre: result = [10 30 50]
;        my-conj [result]: result = [10 30 50]
;      my-map [result] post: result = [10 30 50]
;    my-filter [result] post: result = [10 30 50]
; => [10 30 50]

위의 예제를 실행해 본 결과, 기존에 rfxf를 혼동해 표기한 것은 엄밀한 관점에서 잘못입니다. rf로 표기하는 것이 보다 정확한 것으로 판단됩니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment