Widzę java.util.function.BiFunction, więc mogę to zrobić:
BiFunction<Integer, Integer, Integer> f = (x, y) -> { return 0; };
A jeśli to nie wystarczy i potrzebuję TriFunction? To nie istnieje!
TriFunction<Integer, Integer, Integer, Integer> f = (x, y, z) -> { return 0; };
Chyba powinienem dodać, że wiem, że potrafię zdefiniować własną TriFunction, po prostu próbuję zrozumieć powody, dla których nie uwzględniono jej w standardowej bibliotece.
Odpowiedzi:
O ile wiem, istnieją tylko dwa rodzaje funkcji, destrukcyjne i konstruktywne.
Podczas gdy funkcja konstruktywna, jak sama nazwa wskazuje, coś konstruuje, to destrukcyjna coś niszczy, ale nie w taki sposób, w jaki myślisz teraz.
Na przykład function
jest konstruktywna . Ponieważ musisz coś skonstruować. W tym przykładzie utworzyłeś krotkę (x, y) . Funkcje konstruktywne mają problem polegający na tym, że nie są w stanie obsłużyć nieskończonych argumentów. Ale najgorsze jest to, że nie możesz po prostu pozostawić otwartej kłótni. Nie możesz po prostu powiedzieć „no cóż, niech x: = 1” i wypróbować wszystkie y, które chcesz spróbować. Za każdym razem musisz utworzyć całą krotkę z
x := 1
. Więc jeśli chcesz zobaczyć, co zwracają funkcjey := 1, y := 2, y := 3
, musisz napisaćf(1,1) , f(1,2) , f(1,3)
.W Javie 8 funkcje konstruktywne powinny być obsługiwane (przez większość czasu) przy użyciu odwołań do metod, ponieważ nie ma zbytniej korzyści z używania konstruktywnej funkcji lambda. Są trochę jak metody statyczne. Możesz ich używać, ale nie mają rzeczywistego stanu.
Drugi typ jest destrukcyjny, zabiera coś i demontuje w razie potrzeby. Na przykład funkcja destrukcyjna
robi to samo, co funkcja,
f
która była konstruktywna. Zaletą funkcji destrukcyjnej jest to, że możesz teraz obsługiwać nieskończone argumenty, co jest szczególnie wygodne w przypadku strumieni, i możesz po prostu pozostawić argumenty otwarte. Więc jeśli znowu chcesz zobaczyć, jaki byłby wynik, jeślix := 1
iy := 1 , y := 2 , y := 3
, możesz powiedziećh = g(1)
ih(1)
jest wynikiem dlay := 1
,h(2)
dlay := 2
ih(3)
dlay := 3
.Więc tutaj masz ustalony stan! To dość dynamiczne i przez większość czasu to jest to, czego oczekujemy od lambdy.
Wzorce takie jak Factory są o wiele łatwiejsze, jeśli możesz po prostu wstawić funkcję, która wykonuje pracę za Ciebie.
Niszczycielskie można łatwo łączyć ze sobą. Jeśli typ jest odpowiedni, możesz po prostu skomponować je według własnego uznania. Używając tego, możesz łatwo zdefiniować morfizmy, które znacznie ułatwiają testowanie (przy niezmiennych wartościach)!
Możesz to też zrobić konstruktywną, ale destrukcyjna kompozycja wygląda ładniej i bardziej przypomina listę lub dekorator, a konstruktywna bardzo przypomina drzewo. A rzeczy takie jak wycofywanie się z funkcjami konstruktywnymi są po prostu nieprzyjemne. Możesz po prostu zapisać częściowe funkcje destrukcyjnej (programowanie dynamiczne), a na "backtrack" po prostu użyć starej funkcji destrukcyjnej. To sprawia, że kod jest dużo mniejszy i bardziej czytelny. Dzięki funkcjom konstruktywnym masz mniej więcej do zapamiętania wszystkich argumentów, których może być dużo.
Dlaczego więc istnieje potrzeba, aby
BiFunction
być bardziej kwestionowanym, niż dlaczego nie maTriFunction
?Po pierwsze, przez wiele czasu masz po prostu kilka wartości (mniej niż 3) i potrzebujesz tylko wyniku, więc normalna funkcja niszcząca nie byłaby w ogóle potrzebna, konstruktywna byłaby w porządku. Są rzeczy takie jak monady, które naprawdę potrzebują konstruktywnej funkcji. Ale poza tym nie ma zbyt wielu dobrych powodów, dla których w ogóle istnieje
BiFunction
. Co nie oznacza, że należy go usunąć! Walczę o moje monady, aż umrę!Więc jeśli masz wiele argumentów, których nie możesz połączyć w logiczną klasę kontenera, i jeśli chcesz, aby funkcja była konstruktywna, użyj odwołania do metody. W przeciwnym razie spróbuj użyć nowo zdobytej zdolności funkcji niszczących, może się okazać, że robisz wiele rzeczy z dużo mniejszą liczbą linii kodu.
źródło
BiFunction
została stworzona, aby umożliwić łatwą redukcję danych, a większośćStream
operacji terminalowych to tylko redukcja danych. Dobry przykład jestBinaryOperator<T>
używany w wieluCollectors
. Pierwszy element jest redukowany za pomocą drugiego, a następnie można go redukować następnym i tak dalej. Oczywiście możesz utworzyćFunction<T, Function<T, T>
func = x -> (y -> / * kod redukcji tutaj * /). Ale poważnie? Wszystko to, gdy możesz po prostu zrobićBinaryOperator<T> func = (x, y) -> /*reduction code here*/
. Poza tym to podejście do redukcji danych wydaje mi się bardzo podobne do twojego „destrukcyjnego” podejścia.Function<Integer,Integer> f = (x,y) -> x + y
jest prawidłowa Java, a nią nie jest. Na początek powinna to być funkcja BiFunction!Jeśli potrzebujesz TriFunction, zrób to:
Poniższy mały program pokazuje, jak można go używać. Pamiętaj, że typ wyniku jest określony jako ostatni parametr typu ogólnego.
Wydaje mi się, że gdyby TriFunction miał praktyczne zastosowanie,
java.util.*
czyjava.lang.*
zostałby zdefiniowany. Nigdy nie przekroczyłbym jednak 22 argumentów ;-) Co przez to rozumiem, cały nowy kod, który umożliwia strumieniowe przesyłanie kolekcji, nigdy nie wymagał TriFunction jako żadnego z parametrów metody. Więc to nie zostało uwzględnione.AKTUALIZACJA
Aby uzyskać kompletność i po wyjaśnieniu destrukcyjnych funkcji w innej odpowiedzi (związanej z curry), oto jak można emulować TriFunction bez dodatkowego interfejsu:
Oczywiście istnieje możliwość łączenia funkcji na inne sposoby, np .:
Chociaż currying byłby naturalny dla każdego języka, który obsługuje programowanie funkcjonalne poza lambdami, Java nie jest zbudowana w ten sposób i chociaż jest to możliwe, kod jest trudny do utrzymania, a czasem do odczytania. Jest to jednak bardzo pomocne jako ćwiczenie, a czasami funkcje częściowe mają należne miejsce w kodzie.
źródło
TriFunction<Integer,Integer,Integer,Integer> comp = (x,y,z) -> x + y + z; comp = comp.andThen(s -> s * 2); int result = comp.apply(1, 2, 3); //12
Zobacz stackoverflow.com/questions/19834611/…andThen()
przykład użycia do odpowiedzi.BiFunction
jest używane wStream
API do redukcji danych, co wygląda bardzo podobnie do mojego podejścia do curry: nigdy nie bierzesz więcej niż dwa argumenty i możesz przetwarzać dowolną liczbę elementów, po jednej redukcji na raz (zobacz mój komentarz do zaakceptowanej odpowiedzi, byłbym szczęśliwy, gdyby się mylę, widząc to w ten sposób).Alternatywą jest dodanie poniższej zależności,
Teraz możesz użyć funkcji Vavr, jak poniżej, do 8 argumentów,
3 argumenty:
5 argumentów:
źródło
vavr
biblioteki - to sprawia, że programowanie w stylu funkcjonalnym jest tak znośne w Javie, jak to tylko możliwe.Mam prawie to samo pytanie i częściową odpowiedź. Nie jestem pewien, czy konstruktywna / dekonstrukcyjna odpowiedź jest tym, co mieli na myśli projektanci języka. Myślę, że posiadanie 3 i więcej do N ma ważne przypadki użycia.
Pochodzę z .NET. aw .NET masz Func i Action dla funkcji void. Istnieją również orzeczenia i inne przypadki specjalne. Zobacz: https://msdn.microsoft.com/en-us/library/bb534960(v=vs.110).aspx
Zastanawiam się, jaki był powód, dla którego projektanci języka zdecydowali się na Function, Bfunction i nie kontynuowali do DecaExiFunction?
Odpowiedzią na drugą część jest wymazywanie typów. Po kompilacji nie ma różnicy między Func i Func. W związku z tym nie kompiluje się:
Wewnętrzne funkcje zostały wykorzystane do obejścia innego drobnego problemu. Eclipse nalegał na umieszczenie obu klas w plikach o nazwie Function w tym samym katalogu ... Nie jestem pewien, czy jest to obecnie problem kompilatora. Ale nie mogę zmienić błędu w Eclipse.
Func został użyty, aby zapobiec konfliktom nazw z typem funkcji Java.
Więc jeśli chcesz dodać Func od 3 do 16 argumentów, możesz zrobić dwie rzeczy.
Przykład drugiego sposobu:
i
Jakie byłoby najlepsze podejście?
W powyższych przykładach nie uwzględniłem implementacji metod andThen () i compose (). Jeśli dodasz te, musisz dodać 16 przeciążeń każde: TriFunc powinien mieć andthen () z 16 argumentami. To spowodowałoby błąd kompilacji z powodu zależności cyklicznych. Nie miałbyś również tych przeciążeń dla funkcji i BiFunction. Dlatego powinieneś także zdefiniować Func z jednym argumentem i Func z dwoma argumentami. W .NET zależności cykliczne można by obejść za pomocą metod rozszerzających, które nie są obecne w Javie.
źródło
andThen
16 argumentów? Wynikiem funkcji w Javie jest pojedyncza wartość.andThen
przyjmuje tę wartość i coś z nią robi. Nie ma też problemu z nazewnictwem. Nazwy klas powinny być różne i znajdować się w różnych plikach o tej samej nazwie - zgodnie z logiką ustaloną przez programistów języka Java z funkcjami Function i BiFunction. Ponadto wszystkie te różne nazwy są potrzebne, jeśli typy argumentów są różne. Można utworzyćVargFunction(T, R) { R apply(T.. t) ... }
dla jednego typu.Znalazłem kod źródłowy BiFunction tutaj:
https://github.com/JetBrains/jdk8u_jdk/blob/master/src/share/classes/java/util/function/BiFunction.java
Zmodyfikowałem go, aby utworzyć TriFunction. Podobnie jak BiFunction, używa andThen (), a nie compose (), więc w przypadku niektórych aplikacji, które wymagają compose (), może to nie być odpowiednie. Powinien być odpowiedni dla zwykłych obiektów. Dobry artykuł na temat andThen () i compose () można znaleźć tutaj:
http://www.deadcoderising.com/2015-09-07-java-8-functional-composition-using-compose-and-andthen/
źródło
Możesz także utworzyć własną funkcję, korzystając z 3 parametrów
źródło
Nie możesz zawsze zatrzymać się w TriFunction. Czasami może być konieczne przekazanie n liczby parametrów do funkcji. Następnie zespół pomocy technicznej będzie musiał stworzyć QuadFunction, aby naprawić Twój kod. Długoterminowym rozwiązaniem byłoby utworzenie obiektu z dodatkowymi parametrami, a następnie użycie gotowej funkcji lub funkcji BiFunction.
źródło