클로저 : 감소 대 적용


126

나는 사이의 개념 차이를 이해 reduce하고를 apply:

(reduce + (list 1 2 3 4 5))
; translates to: (+ (+ (+ (+ 1 2) 3) 4) 5)

(apply + (list 1 2 3 4 5))
; translates to: (+ 1 2 3 4 5)

그러나 어느 것이 관용적 클로저입니까? 어떤 식 으로든 다른 점에서 많은 차이가 있습니까? 내 (제한된) 성능 테스트에서 reduce조금 더 빠릅니다.

답변:


125

reduce그리고 apply물론 단지 동등한있는 가변 인수에 대응 케이스에 모든 인수를 볼 필요가 연관 기능 (궁극적 인 결과의 측면에서 반환). 그것들이 결과적으로 동등 할 때, 나는 그것이 일반적으로 apply완벽하게 관용적이지만, 많은 경우 reduce에 동등 하지만 눈 깜박임의 일부를 깎을 수 있다고 말하고 싶습니다 . 다음은 이것을 믿는 나의 근거입니다.

+reduce변수 자체의 경우 두 가지 이상의 인수로 구현됩니다. 실제로, 이것은 가변적, 연관 기능 reduce을 수행하기 위해 엄청나게 합리적인 "기본"방법처럼 보입니다. 아마도 internal-reduce마스터에서 최근에 비활성화 된 1.2 참신을 통해 일을 가속화하기 위해 최적화를 수행 할 가능성이 있습니다. 바라건대 미래에 다시 도입되기를 희망합니다. vararg의 경우에 도움이 될 수있는 모든 기능을 복제하는 것은 어리석은 일입니다. 이러한 일반적인 경우 apply에는 약간의 오버 헤드가 추가됩니다. (실제로 걱정할 필요는 없습니다.)

반면에 복잡한 함수는 일반적으로 구현할 수없는 최적화 기회를 활용할 수 있습니다 reduce. 다음 apply당신이 그 동안 활용할 수 있도록 것입니다 reduce실제로 느려질 수 있습니다. 실제로 후자의 시나리오 발생하는의 좋은 예에 의해 제공된다 str: 그것은 사용 StringBuilder내부적의 사용으로 크게 도움이됩니다 apply보다는 reduce.

그래서, apply의심 스러울 때 사용한다고 말하고 싶습니다 . 그리고 그것이 당신에게 아무것도 사지 않는다는 것을 알게되면 reduce(그리고 이것은 곧 바뀔 것 같지 않다), reduce당신이 그것을 느끼면 그 불필요한 불필요한 오버 헤드를 면도 하는 데 자유롭게 사용 하십시오.


좋은 대답입니다. 참고로, sumhaskell과 같은 내장 함수를 포함하지 않는 이유는 무엇입니까? 꽤 일반적인 작업 인 것 같습니다.
dbyrne

17
고마워요, 기뻐요! Re : sum, Clojure에이 함수가 있다고합니다.이 함수를 호출 +하여 사용할 수 있습니다 apply. 당신이 무엇을 사용하는 - 가변 인자 기능이 제공되는 경우 :-) 진지하게 말하자면, 나는 일반적으로, 리스프 생각, 일반적으로 컬렉션에 래퍼 운영과 함께 아니에요 apply(또는에 대해 reduce당신이 더 의미가 알고있는 경우).
Michał Marczyk

6
재미 있고, 제 충고는 정반대입니다. reduce의심 스러울 apply때, 최적화가 있는지 확실히 알고있을 때. reduce의 계약이 더 정확하여 일반 최적화가 더 쉽습니다. apply더 모호하므로 사례별로 만 최적화 할 수 있습니다. str그리고 concat두 널리 exceptons입니다.
cgrand

1
@cgrand 내 근거의 다른 표현은 기능 것을 대략 수 있습니다 reduceapply결과의 측면에서 동등하다, 내가 그들의 가변 오버로드를 최적화하는 방법을 가장 잘 알고 그냥 용어에서 그것을 구현하는 문제의 함수의 저자를 기대할 수있는 reduce경우 그것이 실제로 가장 의미있는 것입니다 (그렇게하는 옵션은 항상 사용 가능하고 눈에 띄는 기본값을 만듭니다). 그래도 당신이 어디에서 왔는지 reduce확실히 Clojure의 퍼포먼스 스토리의 중심에 있으며 매우 높아지고 명확하게 지정되어 있습니다.
Michał Marczyk

51

이 답변을보고있는 초보자
에게는 조심하지 마십시오.

(apply hash-map [:a 5 :b 6])
;= {:a 5, :b 6}
(reduce hash-map [:a 5 :b 6])
;= {{{:a 5} :b} 6}

21

의견이 다양하다-더 큰 Lisp 세계에서, reduce더 관용적 인 것으로 간주된다. 먼저, 이미 논의 된 가변성 문제가 있습니다. 또한 일부 공통 Lisp 컴파일러는 apply인수 목록을 처리하는 방식으로 인해 매우 긴 목록에 적용될 때 실제로 실패 합니다.

그러나 내 서클의 클로저리스트들 사이 apply에서이 경우에 사용하는 것이 더 일반적 인 것 같습니다. 나는 더 쉽게 이해하고 선호합니다.


19

+는 여러 인수에 적용 할 수있는 특별한 경우이므로이 경우에는 차이가 없습니다. Reduce는 임의의 긴 인수 목록에 고정 된 수의 인수 (2)를 예상하는 함수를 적용하는 방법입니다.


9

나는 일반적으로 모든 종류의 컬렉션에서 작업 할 때 감소를 선호한다는 것을 알았습니다. 그것은 잘 수행되며 일반적으로 매우 유용한 기능입니다.

apply를 사용하는 주된 이유는 매개 변수가 다른 위치에서 다른 것을 의미하거나 초기 매개 변수가 몇 개 있지만 컬렉션에서 나머지를 얻으려는 경우입니다.

(apply + 1 2 other-number-list)

9

이 특정 경우에는 reduce읽기 쉽기 때문에 선호합니다 .

(reduce + some-numbers)

시퀀스를 값으로 바꾸고 있음을 즉시 알고 있습니다.

applyI이 적용되고있는 기능을 고려해야한다 : "아, 그것은이다 +내가 ... 단일 번호를 받고 있어요, 그래서 기능". 약간 덜 간단합니다.


7

+와 같은 간단한 기능을 사용할 때는 실제로 어떤 기능을 사용하든 상관 없습니다.

일반적으로 아이디어는 reduce누적 연산입니다. 누적 함수에 현재 누적 값과 하나의 새 값을 표시합니다. 함수의 결과는 다음 반복에 대한 누적 값입니다. 따라서 반복은 다음과 같습니다.

cum-val[i+1] = F( cum-val[i], input-val[i] )    ; please forgive the java-like syntax!

적용하려면 여러 스칼라 인수를 예상하는 함수를 호출하려고하지만 현재 콜렉션에 있으며 꺼내야합니다. 따라서 말하지 않고

vals = [ val1 val2 val3 ]
(some-fn (vals 0) (vals 1) (vals 2))

우리는 말할 수있다:

(apply some-fn vals)

그리고 다음과 같이 변환됩니다.

(some-fn val1 val2 val3)

따라서 "적용"을 사용하는 것은 시퀀스 주위의 "괄호 제거"와 같습니다.


4

주제에 조금 늦었지만이 예제를 읽은 후 간단한 실험을했습니다. 여기 내 repl의 결과이며 응답에서 아무것도 추론 할 수 없지만 reduce와 apply 사이에 일종의 캐싱 킥이있는 것 같습니다.

user=> (time (reduce + (range 1e3)))
"Elapsed time: 5.543 msecs"
499500
user=> (time (apply + (range 1e3))) 
"Elapsed time: 5.263 msecs"
499500
user=> (time (apply + (range 1e4)))
"Elapsed time: 19.721 msecs"
49995000
user=> (time (reduce + (range 1e4)))
"Elapsed time: 1.409 msecs"
49995000
user=> (time (reduce + (range 1e5)))
"Elapsed time: 17.524 msecs"
4999950000
user=> (time (apply + (range 1e5)))
"Elapsed time: 11.548 msecs"
4999950000

clojure의 소스 코드를 살펴보면 내부 감소로 꽤 깨끗한 재귀가 줄어들고 적용 구현에 대해서는 아무것도 찾지 못했습니다. 내부 호출 감소 적용을위한 +의 Clojure 구현은 repl에 의해 캐시되며 4 번째 호출을 설명하는 것으로 보입니다. 누군가 여기서 실제로 일어나는 일을 분명히 할 수 있습니까?


나는 내가 할 수있을 때마다 감소를 선호한다는 것을 안다 :)
rohit

2
range전화를 time양식 안에 넣지 마십시오 . 시퀀스 구성의 간섭을 제거하기 위해 외부에 두십시오. 제 경우에는 reduce지속적으로 성능이 뛰어납니다 apply.
Davyzhu

3

적용의 아름다움은 함수가 주어지며 (이 경우 +) 엔딩 콜렉션이있는 중간에있는 인수에 의해 형성된 인수 목록에 적용될 수 있습니다. Reduce는 각각에 대해 함수를 적용하는 콜렉션 항목을 처리하기위한 추상화이며 가변 인수 경우와 작동하지 않습니다.

(apply + 1 2 3 [3 4])
=> 13
(reduce + 1 2 3 [3 4])
ArityException Wrong number of args (5) passed to: core/reduce  clojure.lang.AFn.throwArity (AFn.java:429)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.