누군가 Clojure 변환기를 간단한 용어로 설명해 줄 수 있습니까?


100

나는 이것을 읽으려고 시도했지만 여전히 그것들의 가치 나 그들이 대체하는 것을 이해하지 못한다. 그리고 그들은 내 코드를 더 짧고 이해하기 쉽게 만들까요?

최신 정보

많은 사람들이 답변을 올렸지 만, 나 같은 바보라도 이해할 수있는 아주 간단한 것에 대해 트랜스 듀서가 있거나없는 예를 보는 것은 좋을 것입니다. 물론 트랜스 듀서가 어느 정도 높은 수준의 이해를 필요로하지 않는 한,이 경우 절대 이해할 수 없습니다.

답변:


75

트랜스 듀서는 기본 시퀀스가 ​​무엇인지 (어떻게 수행하는지) 모르고 데이터 시퀀스로 무엇을해야하는지 레시피입니다. 모든 시퀀스, 비동기 채널 또는 관찰 가능할 수 있습니다.

그들은 구성 가능하고 다형성입니다.

이점은 새 데이터 소스가 추가 될 때마다 모든 표준 결합자를 구현할 필요가 없다는 것입니다. 계속해서. 결과적으로 사용자는 다른 데이터 소스에서 해당 레시피를 재사용 할 수 있습니다.

광고 업데이트

Clojure 1.7 이전 버전에서는 데이터 흐름 쿼리를 작성하는 세 가지 방법이 있습니다.

  1. 중첩 된 호출
    (reduce + (filter odd? (map #(+ 2 %) (range 0 10))))
  1. 기능성 구성
    (def xform
      (comp
        (partial filter odd?)
        (partial map #(+ 2 %))))
    (reduce + (xform (range 0 10)))
  1. 스레딩 매크로
    (defn xform [xs]
      (->> xs
           (map #(+ 2 %))
           (filter odd?)))
    (reduce + (xform (range 0 10)))

변환기를 사용하면 다음과 같이 작성합니다.

(def xform
  (comp
    (map #(+ 2 %))
    (filter odd?)))
(transduce xform + (range 0 10))

그들은 모두 똑같이합니다. 차이점은 트랜스 듀서를 직접 호출하지 않고 다른 기능으로 전달한다는 것입니다. 변환기는 무엇을해야하는지 알고 있으며 변환기를 가져 오는 기능은 방법을 알고 있습니다. 결합 자의 순서는 스레딩 매크로 (자연 순서)로 작성하는 것과 같습니다. 이제 xform채널과 함께 재사용 할 수 있습니다 .

(chan 1 xform)

3
트랜스 듀서가 시간을 절약하는 방법을 보여주는 예제와 함께 제공되는 답변을 더 찾고있었습니다.
appshare.co

Clojure 또는 일부 데이터 흐름 lib 유지 관리자가 아닌 경우에는 그렇지 않습니다.
Aleš Roubíček 2014 년

5
기술적 결정이 아닙니다. 우리는 비즈니스 가치 기반 의사 결정만을 사용합니다. "그냥 그들을 사용"얻을 것이다 나 해고
appshare.co

1
Clojure 1.7이 출시 될 때까지 트랜스 듀서 사용 시도를 미루면 작업을 더 쉽게 유지할 수 있습니다.
user100464

7
변환기는 다양한 형태의 반복 가능한 객체를 추상화하는 유용한 방법 인 것 같습니다. Clojure seqs와 같은 비 소모성이거나 (비동기 채널과 같은) 소모품 일 수 있습니다. 이와 관련하여, 예를 들어 채널을 사용하여 시퀀스 기반 구현에서 core.async 구현으로 전환하는 경우 변환기를 사용하면 큰 이점을 얻을 수 있습니다. 변환기를 사용하면 로직의 핵심을 변경하지 않고 유지할 수 있습니다. 전통적인 시퀀스 기반 처리를 사용하면 트랜스 듀서 또는 일부 코어 비동기 아날로그를 사용하도록이를 변환해야합니다. 그것이 비즈니스 사례입니다.
Nathan Davis

47

변환기는 효율성을 향상시키고보다 모듈화 된 방식으로 효율적인 코드를 작성할 수 있도록합니다.

이것은 괜찮은 실행 입니다.

이전에 호출을 구성에 비해 map, filter, reduce당신이 그 컬렉션을 각 단계 사이에 중간 컬렉션을 구축하고, 반복적으로 걸을 필요가 없기 때문에 등 당신은 더 나은 성능을 얻을.

reducers모든 작업을 단일 표현식으로 또는 수동으로 구성하는 것과 비교하여 추상화를 사용하기가 더 쉽고, 모듈화가 더 좋으며, 처리 함수를 재사용 할 수 있습니다.


2
궁금한 점은 위에서 "각 단계 사이에 중간 컬렉션을 구축하는 것"이라고 말씀하셨습니다. 그러나 "중간 컬렉션"이 반 패턴처럼 들리지 않습니까? .NET은 lazy enumerable을 제공하고, Java는 lazy 스트림 또는 Guava 기반 이터 러블을 제공하며, lazy Haskell에는 게으른 것이 있어야합니다. 이들 중 어떤 것도 필요로하지 않습니다 map/ reduce그들 모두는 반복자 체인을 구축하기 때문에 중간 컬렉션을 사용할 수 있습니다. 내가 어디에서 잘못 되었습니까?
Lyubomyr Shaydariv

3
중첩 된 경우 클로저 mapfilter중간 컬렉션을 만듭니다.
noisesmith

4
그리고 적어도 Clojure의 게으름과 관련하여 게으름의 문제는 여기서 직교합니다. 예, 맵과 필터는 게으 르며, 연결하면 게으른 값에 대한 컨테이너도 생성됩니다. 머리를 붙 잡지 않으면 필요하지 않은 큰 지연 시퀀스를 구축하지 않지만 여전히 각 지연 요소에 대해 중간 추상화를 구축합니다.
noisesmith 2014-10-12

좋은 예입니다.
appshare.co

8
@LyubomyrShaydariv "중간 컬렉션"에서 noisesmith는 "전체 컬렉션을 반복 / 수정 한 다음 다른 전체 컬렉션을 반복 / 수정"하는 것을 의미하지 않습니다. 즉, 순차를 반환하는 함수 호출을 중첩하면 각 함수 호출이 새로운 순차를 생성합니다. 실제 반복은 여전히 ​​한 번만 발생하지만 중첩 된 순차로 인해 추가 메모리 소비와 객체 할당이 있습니다.
erikprice

22

트랜스 듀서는 기능을 줄이기위한 조합 수단입니다.

예 : 축소 함수는 두 개의 인수 (지금까지의 결과와 입력)를 취하는 함수입니다. 새 결과를 반환합니다 (지금까지). 예 +: 두 개의 인수를 사용하면 첫 번째는 지금까지의 결과로, 두 번째는 입력으로 생각할 수 있습니다.

변환기는 이제 + 기능을 사용하여 두 배 더하기 기능으로 만들 수 있습니다 (추가하기 전에 모든 입력을 두 배로 늘림). 트랜스 듀서는 다음과 같은 모습입니다 (대부분의 기본 용어로).

(defn double
  [rfn]
  (fn [r i] 
    (rfn r (* 2 i))))

그림으로 대체 rfn하여 두 번 이상으로 변환되는 +방법을 확인하십시오 +.

(def twice-plus ;; result of (double +)
  (fn [r i] 
    (+ r (* 2 i))))

(twice-plus 1 2)  ;-> 5
(= (twice-plus 1 2) ((double +) 1 2)) ;-> true

그래서

(reduce (double +) 0 [1 2 3]) 

이제 12를 산출합니다.

변환기에 의해 반환 된 감소 함수는 결과가 어떻게 누적되는지와는 무관합니다. 그 이유는 무의식적으로 전달 된 감소 함수와 함께 축적되기 때문입니다. 여기서 우리는 conj대신 +. Conj컬렉션과 값을 취하고 해당 값이 추가 된 새 컬렉션을 반환합니다.

(reduce (double conj) [] [1 2 3]) 

[2 4 6]

또한 입력 소스의 종류와 무관합니다.

여러 변환기를 (체인 가능한) 레시피로 연결하여 감소 기능을 변환 할 수 있습니다.

업데이트 : 이제 공식 페이지가 있으므로 http://clojure.org/transducers 를 읽어 보는 것이 좋습니다 .


좋은 설명 이었지만 곧 "변환기에 의해 생성 된 감소 기능은 결과가 축적되는 방식과 무관하다"라는 용어에 너무 많이 들어갔습니다.
appshare.co

1
당신 말이 맞습니다. 여기에서 생성 된 단어는 부적절했습니다.
Leon Grapenthin 2014 년

괜찮아. 어쨌든 나는 변압기는 지금, 그래서 어차피 사용하지 않아야 최적화 것으로 알고 있습니다
appshare.co

1
기능을 줄이기위한 조합의 수단입니다. 그 밖에 어디에 있습니까? 이것은 최적화 그 이상입니다.
Leon Grapenthin 2014 년

나는이 대답이 매우 흥미 롭다고 생각하지만 그것이 트랜스 듀서와 어떻게 연결되는지는 분명하지 않습니다 (부분적으로 나는 여전히 주제가 혼란스러워서). double과 의 관계는 무엇입니까 transduce?
Mars

21

일련의 함수를 사용하여 데이터 스트림을 변환한다고 가정 해보십시오. Unix 셸을 사용하면 파이프 연산자로 이런 종류의 작업을 수행 할 수 있습니다.

cat /etc/passwd | tr '[:lower:]' '[:upper:]' | cut -d: -f1| grep R| wc -l

(위의 명령은 사용자 이름에 대문자 또는 소문자 r이있는 사용자 수를 계산합니다.) 이것은 일련의 프로세스로 구현되며 각 프로세스는 이전 프로세스의 출력에서 ​​읽으므로 4 개의 중간 스트림이 있습니다. 5 개의 명령을 단일 집계 명령으로 구성하는 다른 구현을 상상할 수 있습니다.이 명령은 입력에서 읽고 출력을 정확히 한 번만 기록합니다. 중간 스트림이 비싸고 구성이 저렴하다면 좋은 절충안이 될 수 있습니다.

Clojure도 마찬가지입니다. 변환 파이프 라인을 표현하는 방법에는 여러 가지가 있지만 수행 방법에 따라 한 함수에서 다음 함수로 전달되는 중간 스트림으로 끝날 수 있습니다. 데이터가 많은 경우 해당 함수를 단일 함수로 구성하는 것이 더 빠릅니다. 변환기를 사용하면 쉽게 할 수 있습니다. 이전의 Clojure 혁신, 감속기도 그렇게 할 수 있지만 약간의 제한이 있습니다. 변환기는 이러한 제한 사항 중 일부를 제거합니다.

따라서 질문에 답하기 위해 변환기가 반드시 코드를 짧게 또는 더 이해하기 쉽게 만들지는 않지만 코드가 더 길거나 덜 이해하기 쉽지 않을 것이며 많은 데이터로 작업하는 경우 변환기가 코드를 만들 수 있습니다. 더 빨리.

이것은 변환기에 대한 꽤 좋은 개요입니다.


1
아, 트랜스 듀서는 대부분 성능 최적화입니다. 그게 무슨 말입니까?
appshare.co

@Zubair 예, 맞습니다. 최적화는 중간 스트림을 제거하는 것 이상입니다. 병렬로 작업을 수행 할 수도 있습니다.
user100464

2
pmap충분한 관심을 끌지 못하는 것 같은을 언급 할 가치가 있습니다. map시퀀스에 대해 값 비싼 함수를 ping하는 경우 작업을 병렬로 만드는 것은 "p"를 추가하는 것만 큼 쉽습니다. 코드에서 다른 것을 변경할 필요가 없으며 베타가 아닌 알파가 아닌 지금 사용할 수 있습니다. (함수가 중간 시퀀스를 생성하면 변환기가 더 빠를 수 있습니다.)
Mars

10

Rich Hickey는 Strange Loop 2014 컨퍼런스 (45 분)에서 '트랜스 듀서'강연을했습니다.

그는 트랜스 듀서가 무엇인지 간단한 방법으로 설명합니다. 실제 사례는 공항에서 가방 처리입니다. 그는 다른 측면을 명확하게 분리하고 현재 접근 방식과 대조합니다. 끝까지 그는 그들의 존재에 대한 근거를 제시합니다.

비디오 : https://www.youtube.com/watch?v=6mTbuzafcII


8

transducers-js 에서 예제를 읽으면 일상적인 코드에서 어떻게 사용할 수 있는지 구체적으로 이해하는 데 도움이됩니다.

예를 들어, 다음 예제를 고려하십시오 (위 링크의 README에서 가져옴).

var t = require("transducers-js");

var map    = t.map,
    filter = t.filter,
    comp   = t.comp,
    into   = t.into;

var inc    = function(n) { return n + 1; };
var isEven = function(n) { return n % 2 == 0; };
var xf     = comp(map(inc), filter(isEven));

console.log(into([], xf, [0,1,2,3,4])); // [2,4]

하나 xf는 Underscore를 사용하는 일반적인 대안보다 훨씬 깔끔하게 보입니다.

_.filter(_.map([0, 1, 2, 3, 4], inc), isEven);

변환기 예제가 훨씬 더 긴 이유는 무엇입니까? 밑줄 버전은 훨씬 더 간결하게 보인다
appshare.co

1
@Zubair Not reallyt.into([], t.comp(t.map(inc), t.filter(isEven)), [0,1,2,3,4])
Juan Castañeda

7

변환기는 하나의 감소 기능을 사용하고 다른 기능을 반환하는 기능입니다. 감소 기능은

예를 들면 :

user> (def my-transducer (comp count filter))
#'user/my-transducer
user> (my-transducer even? [0 1 2 3 4 5 6])
4
user> (my-transducer #(< 3 %) [0 1 2 3 4 5 6])
3

이 경우 my-transducer는 0에 적용되는 입력 필터링 기능을 사용하고 그 값이 짝수이면? 첫 번째 경우 필터는 해당 값을 카운터로 전달한 후 다음 값을 필터링합니다. 먼저 필터링 한 다음 해당 값을 모두 전달하는 대신 계산합니다.

두 번째 예제에서와 동일하게 한 번에 하나의 값을 확인하고 해당 값이 3보다 작 으면 1을 더할 수 있습니다.


나는이 간단한 설명 좋아
이그나시오

7

변환기의 명확한 정의는 다음과 같습니다.

Transducers are a powerful and composable way to build algorithmic transformations that you can reuse in many contexts, and they’re coming to Clojure core and core.async.

이를 이해하기 위해 다음과 같은 간단한 예를 살펴 보겠습니다.

;; The Families in the Village

(def village
  [{:home :north :family "smith" :name "sue" :age 37 :sex :f :role :parent}
   {:home :north :family "smith" :name "stan" :age 35 :sex :m :role :parent}
   {:home :north :family "smith" :name "simon" :age 7 :sex :m :role :child}
   {:home :north :family "smith" :name "sadie" :age 5 :sex :f :role :child}

   {:home :south :family "jones" :name "jill" :age 45 :sex :f :role :parent}
   {:home :south :family "jones" :name "jeff" :age 45 :sex :m :role :parent}
   {:home :south :family "jones" :name "jackie" :age 19 :sex :f :role :child}
   {:home :south :family "jones" :name "jason" :age 16 :sex :f :role :child}
   {:home :south :family "jones" :name "june" :age 14 :sex :f :role :child}

   {:home :west :family "brown" :name "billie" :age 55 :sex :f :role :parent}
   {:home :west :family "brown" :name "brian" :age 23 :sex :m :role :child}
   {:home :west :family "brown" :name "bettie" :age 29 :sex :f :role :child}

   {:home :east :family "williams" :name "walter" :age 23 :sex :m :role :parent}
   {:home :east :family "williams" :name "wanda" :age 3 :sex :f :role :child}])

마을에 몇 명의 아이들이 있는지 알고 싶습니다. 다음 감속기로 쉽게 찾을 수 있습니다.

;; Example 1a - using a reducer to add up all the mapped values

(def ex1a-map-children-to-value-1 (r/map #(if (= :child (:role %)) 1 0)))

(r/reduce + 0 (ex1a-map-children-to-value-1 village))
;;=>
8

다른 방법은 다음과 같습니다.

;; Example 1b - using a transducer to add up all the mapped values

;; create the transducers using the new arity for map that
;; takes just the function, no collection

(def ex1b-map-children-to-value-1 (map #(if (= :child (:role %)) 1 0)))

;; now use transduce (c.f r/reduce) with the transducer to get the answer 
(transduce ex1b-map-children-to-value-1 + 0 village)
;;=>
8

게다가 하위 그룹을 고려할 때도 정말 강력합니다. 예를 들어 Brown Family에 몇 명의 자녀가 있는지 알고 싶다면 다음을 실행할 수 있습니다.

;; Example 2a - using a reducer to count the children in the Brown family

;; create the reducer to select members of the Brown family
(def ex2a-select-brown-family (r/filter #(= "brown" (string/lower-case (:family %)))))

;; compose a composite function to select the Brown family and map children to 1
(def ex2a-count-brown-family-children (comp ex1a-map-children-to-value-1 ex2a-select-brown-family))

;; reduce to add up all the Brown children
(r/reduce + 0 (ex2a-count-brown-family-children village))
;;=>
2

도움이되는 예를 찾을 수 있기를 바랍니다. 여기에서 더 많은 것을 찾을 수 있습니다

도움이되기를 바랍니다.

클레멘시오 모랄레스 루카스.


3
"변환기는 여러 컨텍스트에서 재사용 할 수있는 알고리즘 변환을 구축하는 강력하고 구성 가능한 방법이며, Clojure 코어 및 core.async에 제공됩니다." 정의가 거의 모든 것에 적용될 수 있습니까?
appshare.co

1
거의 모든 Clojure Transducer에게 말하고 싶습니다.
Clemencio Morales Lucas 2014

6
정의 라기보다는 사명 선언문에 가깝습니다.
화성

4

나는 감소 함수를 대체 할 수있게 됨으로써 시퀀스 함수가 ​​이제 어떻게 확장 될 수 있는지를 설명하는 clojurescript 예제로 이것에 대해 블로그에 올렸다 .

이것이 제가 읽은 트랜스 듀서의 요점입니다. 당신이 생각하는 경우 cons또는 conj하드 등의 작업에 코딩 작업 map, filter등, 환원 기능이 도달 할 수 있었다.

변환기를 사용하면 감소 기능이 분리되고 push변환기 덕분에 네이티브 자바 스크립트 배열로했던 것처럼 대체 할 수 있습니다 .

(transduce (filter #(not (.hasOwnProperty prevChildMapping %))) (.-push #js[]) #js [] nextKeys)

filter 그리고 친구들은 자신의 감소 기능을 제공하는 데 사용할 수있는 변환 기능을 반환하는 새로운 1 arity 작업이 있습니다.


4

여기 내 (대부분) 전문 용어와 코드가없는 답변이 있습니다.

데이터는 스트림 (이벤트와 같이 시간이 지남에 따라 발생하는 값) 또는 구조 (목록, 벡터, 배열 등 특정 시점에 존재하는 데이터)의 두 가지 방식으로 생각할 수 있습니다.

스트림 또는 구조를 통해 수행 할 수있는 특정 작업이 있습니다. 이러한 작업 중 하나가 매핑입니다. 매핑 함수는 각 데이터 항목 (숫자 가정)을 1 씩 증가시킬 수 있으며 이것이 스트림이나 구조에 어떻게 적용될 수 있는지 상상할 수 있습니다.

매핑 함수는 "감소 함수"라고도하는 함수 클래스 중 하나 일뿐입니다. 또 다른 일반적인 감소 기능은 술어와 일치하는 값을 제거하는 필터입니다 (예 : 짝수 인 모든 값 제거).

변환기를 사용하면 하나 이상의 감소 함수 시퀀스를 "포장"하고 스트림 또는 구조 모두에서 작동하는 "패키지"(그 자체가 함수)를 생성 할 수 있습니다. 예를 들어, 일련의 감소 함수를 "패키징"(예 : 짝수를 필터링 한 다음 결과 번호를 매핑하여 1 씩 증가) 한 다음 해당 변환기 "패키지"를 값의 스트림이나 구조 (또는 둘 다)에 사용할 수 있습니다. .

그래서 이것에 대해 특별한 것은 무엇입니까? 일반적으로 스트림과 구조 모두에서 작동하도록 효율적으로 구성 할 수없는 감소 함수입니다.

따라서 이러한 기능에 대한 지식을 활용하여 더 많은 사용 사례에 적용 할 수 있다는 이점이 있습니다. 비용은이 추가 전력을 제공하기 위해 추가 기계 (예 : 변환기)를 배워야한다는 것입니다.


2

내가 아는 한, 그것들은 빌딩 블록 과 같습니다. 입력 및 출력 구현에서 분리 된 . 작업을 정의하기 만하면됩니다.

작업의 구현이 입력의 코드에 있지 않고 출력으로 아무것도 수행되지 않으므로 변환기는 매우 재사용이 가능합니다. 그들은 Akka StreamsFlow s를 생각 나게합니다. .

나는 또한 변환기를 처음 사용합니다. 불분명 한 대답을 유감스럽게 생각합니다.


1

이 게시물은 변환기에 대한 더 많은 조감도를 제공합니다.

https://medium.com/@roman01la/understanding-transducers-in-javascript-3500d3bd9624


3
외부 링크에만 의존하는 답변은 향후 언제든지 링크가 끊어 질 수 있으므로 SO를 사용하지 않는 것이 좋습니다. 대신 답에 내용을 인용하십시오.
Vincent Cantin

@VincentCantin 사실, Medium 게시물이 삭제되었습니다.
Dmitri Zaitsev

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.