2010-06-30 24 views
111

Tôi hiểu sự khác biệt về khái niệm giữa reduceapply:Clojure: giảm so với áp dụng

(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) 

Tuy nhiên, đó là một trong clojure nhiều thành ngữ? Liệu nó có khác biệt nhiều theo cách này hay cách khác? Từ thử nghiệm hiệu suất (có giới hạn) của tôi, có vẻ như reduce nhanh hơn một chút.

Trả lời

107

reduceapply là tất nhiên chỉ tương đương (về kết quả cuối cùng trả lại) cho các chức năng kết hợp mà cần phải nhìn thấy tất cả các đối số của họ trong trường hợp biến-arity. Khi chúng tương đương với kết quả, tôi muốn nói rằng apply luôn hoàn toàn thành ngữ, trong khi reduce là tương đương - và có thể cạo một phần nhỏ trong chớp mắt - trong rất nhiều trường hợp phổ biến. Điều gì sau đây là lý do của tôi để tin điều này.

+ tự nó được triển khai theo điều khoản reduce cho trường hợp biến thiên (nhiều hơn 2 đối số). Thật vậy, điều này có vẻ như một cách "mặc định" vô cùng hợp lý để đi đến bất kỳ biến số nào, chức năng liên kết: reduce có khả năng thực hiện một số tối ưu hóa để tăng tốc độ - có lẽ thông qua một cái gì đó như internal-reduce, 1.2 , nhưng hy vọng sẽ được giới thiệu lại trong tương lai - mà nó sẽ là ngớ ngẩn để nhân rộng trong mọi chức năng mà có thể được hưởng lợi từ chúng trong trường hợp vararg. Trong các trường hợp phổ biến như vậy, apply sẽ chỉ thêm một khoản phí nhỏ. (Lưu ý rằng không có gì phải lo lắng.)

Mặt khác, một hàm phức tạp có thể tận dụng một số cơ hội tối ưu hóa không đủ chung để được xây dựng thành reduce; sau đó apply sẽ cho phép bạn tận dụng lợi thế của những người trong khi reduce thực sự có thể làm chậm bạn xuống. Một ví dụ điển hình về trường hợp thứ hai xảy ra trong thực tế được cung cấp bởi str: sử dụng một số StringBuilder nội bộ và sẽ được hưởng lợi đáng kể từ việc sử dụng apply thay vì reduce.

Vì vậy, tôi muốn nói sử dụng apply khi nghi ngờ; và nếu bạn tình cờ biết rằng nó không mua cho bạn bất cứ thứ gì trên reduce (và điều này có thể không thay đổi rất sớm), hãy sử dụng reduce để cạo bỏ chi phí không cần thiết nhỏ bé nếu bạn cảm thấy thích.

+0

Câu trả lời hay. Một lưu ý phụ, tại sao không bao gồm một hàm 'sum' tích hợp như trong haskell? Có vẻ như một hoạt động khá phổ biến. – dbyrne

+14

Cảm ơn, rất vui khi được nghe điều đó! Re: 'sum', tôi muốn nói rằng Clojure có hàm này, nó được gọi là' + 'và bạn có thể sử dụng nó với' apply'. :-) Nghiêm túc nói, tôi nghĩ rằng trong Lisp, nói chung, nếu một hàm variadic được cung cấp, nó không thường đi kèm với một wrapper hoạt động trên bộ sưu tập - đó là những gì bạn sử dụng 'apply' cho (hoặc' reduce', nếu bạn biết Điều đó có ý nghĩa hơn). –

+4

Vui, lời khuyên của tôi là ngược lại: 'giảm bớt' khi nghi ngờ, 'áp dụng' khi bạn biết chắc chắn có tối ưu hóa. Hợp đồng 'reduce' là chính xác hơn và do đó dễ bị tối ưu hóa chung hơn. 'apply' là mơ hồ hơn và do đó chỉ có thể được tối ưu hóa theo từng trường hợp. 'str' và' concat' là hai excepton phổ biến. – cgrand

13

Nó không tạo sự khác biệt trong trường hợp này, vì + là trường hợp đặc biệt có thể áp dụng cho bất kỳ số đối số nào. Giảm là một cách để áp dụng một hàm mong đợi một số đối số cố định (2) vào một danh sách các đối số dài tùy ý.

9

Tôi thường thấy mình thích giảm khi hành động trên bất kỳ loại bộ sưu tập nào - nó hoạt động tốt và là một chức năng khá hữu ích nói chung.

Lý do chính tôi áp dụng là nếu tham số có ý nghĩa khác nhau ở các vị trí khác nhau hoặc nếu bạn có một vài thông số ban đầu nhưng muốn nhận phần còn lại từ bộ sưu tập, ví dụ:

(apply + 1 2 other-number-list) 
18

Ý kiến ​​khác nhau- Trong thế giới Lisp lớn hơn, reduce chắc chắn được coi là thành ngữ hơn. Đầu tiên, có những vấn đề variadic đã được thảo luận. Ngoài ra, một số trình biên dịch Lisp thường sẽ thực sự thất bại khi apply được áp dụng trong danh sách rất dài vì cách chúng xử lý danh sách đối số.

Trong số các Clojurists trong vòng kết nối của tôi, mặc dù, sử dụng apply trong trường hợp này có vẻ phổ biến hơn. Tôi thấy nó dễ dàng hơn và cũng thích nó hơn.

41

Đối với người mới nhìn vào câu trả lời này,
cẩn thận, họ không giống nhau:

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

ví dụ tuyệt vời, cảm ơn! =) – sova

5

Trong trường hợp cụ thể này, tôi thích reduce vì nó hơn có thể đọc được: khi tôi đọc

(reduce + some-numbers) 

Tôi biết ngay lập tức bạn đang chuyển chuỗi thành một giá trị.

Với apply Tôi phải xem xét chức năng nào đang được áp dụng: "ah, đó là chức năng +, vì vậy tôi nhận được ... một số duy nhất". Hơi đơn giản hơn.

4

Khi sử dụng chức năng đơn giản như +, thực sự bạn không sử dụng chức năng nào.

Nói chung, ý tưởng là giảm là một hoạt động tích lũy. Bạn trình bày giá trị tích lũy hiện tại và một giá trị mới cho hàm tích lũy của bạn, với kết quả là giá trị cộng dồn cho lần lặp tiếp theo. Vì vậy, lặp đi lặp lại của bạn trông giống như:

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

Đối với áp dụng, ý tưởng là bạn đang cố gắng để gọi một chức năng hy vọng một số lập luận vô hướng, nhưng họ hiện đang ở trong một bộ sưu tập và cần phải được kéo ra. Vì vậy, thay vì nói:

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

chúng ta có thể nói:

(apply some-fn vals) 

và nó được chuyển thành tương đương với:

(some-fn val1 val2 val3) 

Vì vậy, sử dụng "áp dụng" giống như "loại bỏ các dấu ngoặc đơn "xung quanh chuỗi.

3

Bit muộn về chủ đề nhưng tôi đã làm một thử nghiệm đơn giản sau khi đọc ví dụ này. Đây là kết quả từ repl của tôi, tôi chỉ không thể suy ra bất cứ điều gì từ phản ứng, nhưng có vẻ như có một số loại bộ nhớ đệm đá ở giữa giảm và áp dụng.

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 

Nhìn vào mã nguồn của clojure làm giảm việc đệ quy khá sạch sẽ với nội bộ giảm, không tìm thấy bất kỳ điều gì khi triển khai áp dụng. Clojure thực hiện của + cho áp dụng nội bộ gọi giảm, được lưu trữ bởi repl, mà dường như giải thích cuộc gọi thứ tư. Ai đó có thể làm rõ những gì thực sự xảy ra ở đây?

+0

Tôi biết tôi sẽ thích giảm bất cứ khi nào tôi có thể :) – rohit

+1

Bạn không nên đặt lệnh 'phạm vi' bên trong biểu mẫu' thời gian'. Đặt nó bên ngoài để loại bỏ sự can thiệp của xây dựng chuỗi. Trong trường hợp của tôi, 'reduce' luôn hoạt động tốt hơn' apply'. – Davyzhu

Các vấn đề liên quan