Jednak który z nich jest bardziej idiomatyczny? Czy ma to duże znaczenie w taki czy inny sposób? Z moich (ograniczonych) testów wydajności wynika, że reducejest nieco szybszy.
reducei applysą oczywiście równoważne tylko (pod względem zwróconego ostatecznego wyniku) dla funkcji asocjacyjnych, które muszą widzieć wszystkie swoje argumenty w przypadku zmiennej-arity. Kiedy są równoważne wynikom, powiedziałbym, że applyzawsze jest to idealnie idiomatyczne, podczas gdyreduce jest równoważne - i może skrócić o ułamek mrugnięcia oka - w wielu typowych przypadkach. Oto moje uzasadnienie, aby w to wierzyć.
+jest zaimplementowany w kategoriach reducedla przypadku zmiennej-arity (więcej niż 2 argumenty). Rzeczywiście, wydaje się to niezmiernie rozsądnym "domyślnym" sposobem na zastosowanie dowolnej funkcji asocjacyjnej o zmiennej arsenale: reducema potencjał do wykonania pewnych optymalizacji, aby przyspieszyć działanie - być może poprzez coś w rodzaju internal-reducenowości 1.2 ostatnio wyłączonej w trybie głównym, ale miejmy nadzieję, że zostaną ponownie wprowadzone w przyszłości - co byłoby głupio powielać w każdej funkcji, która mogłaby z nich skorzystać w przypadku vararg. W takich typowych przypadkach applypo prostu doda trochę narzutów. (Zauważ, że nie ma się czym martwić.)
Z drugiej strony, funkcja złożona może wykorzystywać pewne możliwości optymalizacji, które nie są na tyle ogólne, aby można je było wbudować reduce; następnie applypozwolili skorzystać z tych chwili reducerzeczywiście może spowolnić. Dobrym przykładem tego drugiego scenariusza występującego w praktyce jest str: wykorzystuje StringBuilderwewnętrznie i odniesie znaczne korzyści z użycia applyzamiast reduce.
Więc powiedziałbym, że używaj applyw razie wątpliwości; a jeśli zdarzy ci się wiedzieć, że nie kupuje ci to niczego ponad reduce(i że jest mało prawdopodobne, aby to się wkrótce zmieniło), możesz swobodnie reduceogolić to drobne niepotrzebne obciążenie, jeśli masz na to ochotę.
Świetna odpowiedź. Na marginesie, dlaczego nie uwzględnić wbudowanej sumfunkcji, takiej jak w haskell? Wydaje się, że to dość powszechna operacja.
dbyrne
17
Dzięki, miło mi to słyszeć! Re sum:, powiedziałbym, że Clojure ma tę funkcję, nazywa się +i możesz jej używać apply. :-) Poważnie mówiąc, myślę, że generalnie w Lispie, jeśli zapewnia się funkcję wariadyczną, zwykle nie towarzyszy jej opakowanie działające na kolekcjach - do tego używasz apply(lub reduce, jeśli wiesz, ma to większy sens).
Michał Marczyk
6
Zabawne, moja rada jest odwrotna: kiedy masz reducewątpliwości, applykiedy wiesz na pewno, istnieje optymalizacja. reduceumowa jest bardziej precyzyjna, a przez to bardziej podatna na ogólną optymalizację. applyjest bardziej niejasny i dlatego może być optymalizowany tylko dla poszczególnych przypadków. stri concatsą dwoma powszechnymi wyjątkami.
cgrand
1
@cgrand Przeformułowanie mojego uzasadnienia może z grubsza polegać na tym, że w przypadku funkcji, które są równoważne pod względem wyników reducei applysą równoważne pod względem wyników, spodziewałbym się, że autor danej funkcji będzie wiedział, jak najlepiej zoptymalizować ich wariadyczne przeciążenie i po prostu zaimplementować je w kategoriach, reducejeśli to jest rzeczywiście najbardziej sensowne (taka opcja jest z pewnością zawsze dostępna i stanowi wyjątkowo rozsądną wartość domyślną). Widzę jednak, skąd pochodzisz, reducejest zdecydowanie kluczem do historii wydajności Clojure (i coraz bardziej), bardzo dobrze zoptymalizowanej i bardzo jasno określonej.
Michał Marczyk 22.08.13
51
Dla początkujących, którzy patrzą na tę odpowiedź,
bądź ostrożny, to nie to samo:
Opinie są różne - w większym świecie Lispów reducejest zdecydowanie bardziej idiomatyczny. Po pierwsze, omówiono już różne kwestie. Ponadto niektóre kompilatory Common Lisp będą faktycznie zawieść, gdyapply zostaną zastosowane do bardzo długich list z powodu sposobu, w jaki obsługują listy argumentów.
Jednak wśród Clojurists w moim kręgu używanie applyw tym przypadku wydaje się bardziej powszechne. Łatwiej mi się bawić i wolę to też.
W tym przypadku nie ma to znaczenia, ponieważ + to specjalny przypadek, który można zastosować do dowolnej liczby argumentów. Reduce to sposób na zastosowanie funkcji, która oczekuje stałej liczby argumentów (2) na dowolnie długiej liście argumentów.
Zwykle wolę redukować, gdy działam na jakimkolwiek rodzaju kolekcji - działa to dobrze i ogólnie jest całkiem użyteczną funkcją.
Głównym powodem, dla którego użyłbym Apply, jest to, że parametry oznaczają różne rzeczy w różnych pozycjach lub jeśli masz kilka parametrów początkowych, ale chcesz pobrać resztę z kolekcji, np.
Kiedy używasz prostej funkcji, takiej jak +, naprawdę nie ma znaczenia, której używasz.
Generalnie chodzi o to, że reducejest to operacja akumulacyjna. Przedstawiasz bieżącą wartość akumulacji i jedną nową wartość swojej funkcji sumującej. Wynikiem funkcji jest skumulowana wartość dla następnej iteracji. Twoje iteracje wyglądają więc następująco:
cum-val[i+1] = F( cum-val[i], input-val[i]); please forgive the java-like syntax!
W przypadku zastosowania chodzi o to, że próbujesz wywołać funkcję oczekującą wielu argumentów skalarnych, ale obecnie znajdują się one w kolekcji i muszą zostać wyciągnięte. Więc zamiast mówić:
Trochę za późno na ten temat, ale po przeczytaniu tego przykładu przeprowadziłem prosty eksperyment. Oto wynik z mojej odpowiedzi, po prostu nie mogę nic wywnioskować z odpowiedzi, ale wydaje się, że między redukcją a zastosowaniem jest jakiś rodzaj buforowania.
Patrząc na kod źródłowy clojure, zmniejszając jego całkiem czystą rekursję za pomocą funkcji Internal-Red, nie znalazłem nic na temat implementacji Apply. Implementacja Clojure + dla zastosuj wewnętrznie wywołaj redukuj, która jest buforowana przez repl, co wydaje się wyjaśniać czwarte wywołanie. Czy ktoś może wyjaśnić, co się tu naprawdę dzieje?
Nie należy umieszczać rangepołączenia wewnątrz timeformularza. Umieść go na zewnątrz, aby usunąć zakłócenia konstrukcji sekwencji. W moim przypadku reducekonsekwentnie przewyższa apply.
Davyzhu,
3
Piękno zastosowania polega na tym, że funkcja (w tym przypadku +) może być zastosowana do listy argumentów utworzonej przez poprzedzające argumenty interweniujące z końcową kolekcją. Reduce jest abstrakcją służącą do przetwarzania elementów kolekcji, stosując funkcję dla każdego z nich i nie działa ze zmiennymi przypadkami args.
(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)
sum
funkcji, takiej jak w haskell? Wydaje się, że to dość powszechna operacja.sum
:, powiedziałbym, że Clojure ma tę funkcję, nazywa się+
i możesz jej używaćapply
. :-) Poważnie mówiąc, myślę, że generalnie w Lispie, jeśli zapewnia się funkcję wariadyczną, zwykle nie towarzyszy jej opakowanie działające na kolekcjach - do tego używaszapply
(lubreduce
, jeśli wiesz, ma to większy sens).reduce
wątpliwości,apply
kiedy wiesz na pewno, istnieje optymalizacja.reduce
umowa jest bardziej precyzyjna, a przez to bardziej podatna na ogólną optymalizację.apply
jest bardziej niejasny i dlatego może być optymalizowany tylko dla poszczególnych przypadków.str
iconcat
są dwoma powszechnymi wyjątkami.reduce
iapply
są równoważne pod względem wyników, spodziewałbym się, że autor danej funkcji będzie wiedział, jak najlepiej zoptymalizować ich wariadyczne przeciążenie i po prostu zaimplementować je w kategoriach,reduce
jeśli to jest rzeczywiście najbardziej sensowne (taka opcja jest z pewnością zawsze dostępna i stanowi wyjątkowo rozsądną wartość domyślną). Widzę jednak, skąd pochodzisz,reduce
jest zdecydowanie kluczem do historii wydajności Clojure (i coraz bardziej), bardzo dobrze zoptymalizowanej i bardzo jasno określonej.Dla początkujących, którzy patrzą na tę odpowiedź,
bądź ostrożny, to nie to samo:
źródło
Opinie są różne - w większym świecie Lispów
reduce
jest zdecydowanie bardziej idiomatyczny. Po pierwsze, omówiono już różne kwestie. Ponadto niektóre kompilatory Common Lisp będą faktycznie zawieść, gdyapply
zostaną zastosowane do bardzo długich list z powodu sposobu, w jaki obsługują listy argumentów.Jednak wśród Clojurists w moim kręgu używanie
apply
w tym przypadku wydaje się bardziej powszechne. Łatwiej mi się bawić i wolę to też.źródło
W tym przypadku nie ma to znaczenia, ponieważ + to specjalny przypadek, który można zastosować do dowolnej liczby argumentów. Reduce to sposób na zastosowanie funkcji, która oczekuje stałej liczby argumentów (2) na dowolnie długiej liście argumentów.
źródło
Zwykle wolę redukować, gdy działam na jakimkolwiek rodzaju kolekcji - działa to dobrze i ogólnie jest całkiem użyteczną funkcją.
Głównym powodem, dla którego użyłbym Apply, jest to, że parametry oznaczają różne rzeczy w różnych pozycjach lub jeśli masz kilka parametrów początkowych, ale chcesz pobrać resztę z kolekcji, np.
źródło
W tym konkretnym przypadku wolę,
reduce
ponieważ jest bardziej czytelny : kiedy czytamWiem od razu, że zmieniasz sekwencję w wartość.
Z
apply
muszę rozważyć, która funkcja jest stosowana: „Ach, to jest to+
funkcja, więc jestem coraz ... jeden numer”. Nieco mniej proste.źródło
Kiedy używasz prostej funkcji, takiej jak +, naprawdę nie ma znaczenia, której używasz.
Generalnie chodzi o to, że
reduce
jest to operacja akumulacyjna. Przedstawiasz bieżącą wartość akumulacji i jedną nową wartość swojej funkcji sumującej. Wynikiem funkcji jest skumulowana wartość dla następnej iteracji. Twoje iteracje wyglądają więc następująco:W przypadku zastosowania chodzi o to, że próbujesz wywołać funkcję oczekującą wielu argumentów skalarnych, ale obecnie znajdują się one w kolekcji i muszą zostać wyciągnięte. Więc zamiast mówić:
możemy powiedzieć:
i jest konwertowany na odpowiednik:
Zatem użycie „zastosuj” jest jak „usunięcie nawiasów” wokół sekwencji.
źródło
Trochę za późno na ten temat, ale po przeczytaniu tego przykładu przeprowadziłem prosty eksperyment. Oto wynik z mojej odpowiedzi, po prostu nie mogę nic wywnioskować z odpowiedzi, ale wydaje się, że między redukcją a zastosowaniem jest jakiś rodzaj buforowania.
Patrząc na kod źródłowy clojure, zmniejszając jego całkiem czystą rekursję za pomocą funkcji Internal-Red, nie znalazłem nic na temat implementacji Apply. Implementacja Clojure + dla zastosuj wewnętrznie wywołaj redukuj, która jest buforowana przez repl, co wydaje się wyjaśniać czwarte wywołanie. Czy ktoś może wyjaśnić, co się tu naprawdę dzieje?
źródło
range
połączenia wewnątrztime
formularza. Umieść go na zewnątrz, aby usunąć zakłócenia konstrukcji sekwencji. W moim przypadkureduce
konsekwentnie przewyższaapply
.Piękno zastosowania polega na tym, że funkcja (w tym przypadku +) może być zastosowana do listy argumentów utworzonej przez poprzedzające argumenty interweniujące z końcową kolekcją. Reduce jest abstrakcją służącą do przetwarzania elementów kolekcji, stosując funkcję dla każdego z nich i nie działa ze zmiennymi przypadkami args.
źródło