Câu trả lời của Eran đã mô tả sự khác biệt giữa các phiên bản hai-arg và ba-arg reduce
trong đó phiên bản trước giảm Stream<T>
xuống T
trong khi phiên bản sau giảm Stream<T>
xuống U
. Tuy nhiên, nó không thực sự giải thích sự cần thiết của chức năng kết hợp bổ sung khi giảm Stream<T>
xuống U
.
Một trong những nguyên tắc thiết kế của API luồng là API không nên khác nhau giữa các luồng tuần tự và song song hoặc đặt một cách khác, một API cụ thể không nên ngăn luồng chạy chính xác theo tuần tự hoặc song song. Nếu lambdas của bạn có các thuộc tính phù hợp (kết hợp, không can thiệp, v.v.), một luồng chạy tuần tự hoặc song song sẽ cho kết quả tương tự.
Trước tiên chúng ta hãy xem xét phiên bản giảm hai đối số:
T reduce(I, (T, T) -> T)
Việc thực hiện tuần tự là đơn giản. Giá trị danh tính I
được "tích lũy" với phần tử luồng zeroth để đưa ra kết quả. Kết quả này được tích lũy với phần tử luồng đầu tiên để đưa ra kết quả khác, lần lượt được tích lũy với phần tử luồng thứ hai, v.v. Sau khi phần tử cuối cùng được tích lũy, kết quả cuối cùng được trả về.
Việc thực hiện song song bắt đầu bằng cách chia luồng thành các phân đoạn. Mỗi phân đoạn được xử lý bởi luồng riêng của nó theo kiểu tuần tự mà tôi đã mô tả ở trên. Bây giờ, nếu chúng ta có N luồng, chúng ta có N kết quả trung gian. Những điều này cần phải được giảm xuống một kết quả. Vì mỗi kết quả trung gian thuộc loại T và chúng tôi có một số kết quả, chúng tôi có thể sử dụng cùng một hàm tích lũy để giảm N kết quả trung gian đó xuống một kết quả duy nhất.
Bây giờ hãy xem xét một hoạt động giảm hai đối số giả thuyết mà giảm Stream<T>
xuống U
. Trong các ngôn ngữ khác, đây được gọi là thao tác "gấp" hoặc "gập sang trái" vì vậy đó là những gì tôi sẽ gọi nó ở đây. Lưu ý điều này không tồn tại trong Java.
U foldLeft(I, (U, T) -> U)
(Lưu ý rằng giá trị nhận dạng I
thuộc loại U.)
Phiên bản tuần tự foldLeft
giống như phiên bản tuần tự reduce
ngoại trừ các giá trị trung gian thuộc loại U thay vì loại T. Nhưng mặt khác thì giống nhau. (Một foldRight
thao tác giả định sẽ tương tự ngoại trừ các thao tác sẽ được thực hiện từ phải sang trái thay vì từ trái sang phải.)
Bây giờ hãy xem xét phiên bản song song của foldLeft
. Hãy bắt đầu bằng cách chia luồng thành các phân đoạn. Sau đó chúng ta có thể có mỗi luồng N giảm các giá trị T trong phân đoạn của nó thành N giá trị trung gian của loại U. Bây giờ thì sao? Làm thế nào để chúng ta nhận được từ N giá trị của loại U xuống một kết quả duy nhất của loại U?
Điều còn thiếu là một hàm khác kết hợp nhiều kết quả trung gian của loại U thành một kết quả duy nhất của loại U. Nếu chúng ta có một hàm kết hợp hai giá trị U thành một, điều đó đủ để giảm bất kỳ số lượng giá trị nào xuống còn một - giống như việc giảm ban đầu ở trên. Do đó, hoạt động rút gọn mang lại kết quả của một loại khác nhau cần hai chức năng:
U reduce(I, (U, T) -> U, (U, U) -> U)
Hoặc, sử dụng cú pháp Java:
<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
Tóm lại, để thực hiện giảm song song thành một loại kết quả khác nhau, chúng ta cần hai hàm: một hàm tích lũy các phần tử T thành các giá trị U trung gian và thứ hai kết hợp các giá trị U trung gian thành một kết quả U duy nhất. Nếu chúng ta không chuyển loại, thì hóa ra hàm tích lũy giống như hàm combiner. Đó là lý do tại sao việc giảm xuống cùng loại chỉ có chức năng tích lũy và việc giảm xuống một loại khác đòi hỏi các chức năng tích lũy và kết hợp riêng biệt.
Cuối cùng, Java không cung cấp foldLeft
và foldRight
hoạt động vì chúng ngụ ý một thứ tự cụ thể của các hoạt động vốn là tuần tự. Điều này đụng độ với nguyên tắc thiết kế đã nêu ở trên về việc cung cấp các API hỗ trợ hoạt động tuần tự và song song như nhau.