Pytanie czy w sytuacji gdy chcę napisać po prostu kilka kawałków kodu potrzebny jest jakiś rozbudowany wstęp? Sam sobie odpowiem, że nie i dlatego tylko wspomnę, że będzie tutaj o konstrukcji, która może znacznie poprawić czytelność kodu gdy w naszym kodzie Javowym (tak Javowym) pojawi się koło siebie kilka Optionów czy Futureów.
W Javie8 i są już i lamby i są już reprezentacje "Efektów". I chyba każdy język, który ma lambdy i efekty ma coś co nazywa się "[coś-tam] comprehension" - no tylko Java nie ma... ale w sumie to trochę ma... ale nie do końca. Zresztą zaraz zobaczymy.
Po co w ogóle to comprehension
Nie będziemy się bawić w tworzenie jakiejś sztucznej domeny tylko po prostu jedziemy z trzema optionami.
val option1=Some(1) val option2=Some(2) val option3=Some(3)
Jak mamy taki option to możemy kombinatorem/metodą map przetwarzać potencjalną wartość, która weń siedzi przy pomocy takich "czystych" normalnych funkcji.
option1.map(i=>s"result is $i").getOrElse("there is no result") //res1: String = result is 1
To było proste ale bardziej skomplikowane konstrukcje pojawią się gdy więcej Option-ów musi ze sobą współpracować.I jeśli chce dodać dwa inty z option1 i option2 to będzie to wyglądać tak :
option1.flatMap{i1 => option2.map(i2=>i1+i2) }I analogicznie dla trzech :
val result: Option[String] = option1.flatMap { i1 => option2.flatMap { i2 => option3.map(i3 => s"result is ${i1 + i2 + i3}") } }I tak dalej...
Ponieważ im więcej tego będzie tym większy arrow code się wygeneruje dlatego też w Scali mamy do dyspozycji taką oto konstrukcję :
val forResult: Option[String] =for{ i1 <- option1 i2 <- option2 i3 <- option3 } yield s"result is ${i1+i2+i3}" result.getOrElse("there is no result")Wynik ten sam a jak się zna ten piękny pokazywany tutaj język to czyta się o niebo łatwiej.
A co jest w Javie? A w Javie...
Mroczne widmo
Przy pomocy standardowych bibliotek w Javie to "se" można takie coś najwyżej zrobić :
Optional<Integer> optional1 = Optional.of(1); Optional<Integer> optional2 = Optional.of(2); Optional<Integer> optional3 = Optional.of(3); Optional<String> result = optional1.flatMap(i1 -> optional2.flatMap(i2 -> optional3.map(i3 -> "result is : " + (i1 + i2 + i3)) ) ); System.out.println(result.orElse("there is no result")); // result is : 6
Ale nie jest jeszcze tak źle bo ten... epizod sżósty... i tak dalej
Nowa nadzieja
Jacyś bardzo dobrzy ludzie - niech im szczęście w życiu sprzyja - stworzyli bibliotekę JAVASLANG , która po pierwsze primo daje nam typ Option, który nie jest tka upośledzony jak Optional z Javy8 (z 5 razy więcej przydatnych nań jest metod). No i bardzo wygodnie się pomiędzy tymi dwoma mapuje.
Option<Integer> dobryOption = Option.ofOptional(Optional.of(1)); Optional<Integer> dziwnyOption = dobryOption.toJavaOptional();
Ale najważniejsze jest to teraz, patrz na to , ej no skup się teraz, przypomnij sobie te flaMapy z poprzedniego punktu i patrz na to, patrz teraz na to :
API.For(Option.of(1),Option.of(2),Option.of(3)) .yield((i1,i2,i3)->i1+i2+i3) .map(sum -> "result is : "+sum) .forEach(System.out::println); //result is : 6
To jest chyba nówka sztuka : http://static.javadoc.io/io.javaslang/javaslang/2.0.2/javaslang/API.html
Labola.. Rabor... Rabol ... Labolatorium
Scala - wszystkie Optiony Some
for { i1 <- Option(1) i2 <- Option(2) i3 <- Option(3) } yield i1+i2+i3 //Some(6)
Javaslang - wszystkie Optiony Some
API.For(Option.of(1), Option.of(2), Option.of(3)) .yield((i1, i2, i3) -> i1 + i2 + i3).headOption() //Some(6)
Scala - jeden Option None
val result: Option[Int] =for { i1 <- Option(1) i2 <- Option.empty[Int] i3 <- Option(3) } yield i1+i2+i3 //None
JavaSlang - jeden Option None
Option<Integer> result = API.For(Option.of(1), Option.<Integer>none(), Option.of(3)) .yield((i1, i2, i3) -> i1 + i2 + i3).headOption(); //None
Scala - Interakcja pomiędzy Optionami
val result: Option[Int] =for { i1 <- Option(1) i2 <- Option(2 + i1) i3 <- Option(3 + i2) } yield i1+i2+i3 //Some(10)
Javaslang - Interakcja pomiędzy Optionami
Tutaj trzeba zwrócić uwagę, że składnia jest ciutkę inna ale z kompilatorem Javy chyba nic lepszego się nie zdziała :(
Option<Integer> result = For(Option.of(1), i1 -> For(Option.of(2 + i1), i2 -> For(Option.of(3 + i2)).yield(i3 -> i1 + i2 + i3) ) ).headOption(); //Some(10)
No to już tak średnio wygląda ale to nie największy problem...
"Ej a ten headOption?" - czyli nie ma róży bez kolców a ślimak pokaż rogi gdzie kucharek sześć
Trochę tam oszukiwaliśmy w tym Javaslang bo zawsze po wszystkim wołaliśmy "headOption". A stosowaliśmy to gdyż oryginalny typ to :
Iterator<Integer>
Czyli dostajemy osobny typ zdefiniowany przez Javaslang
package javaslang.collection; public interface Iterator<T> extends java.util.Iterator<T>, Traversable<T> {...}Zaś sama deklaracja For wygląda tak :
public static <T1, T2> For2<T1, T2> For(Iterable<T1> ts1, Iterable<T2> ts2)Dlatego możemy zapomnieć o użyciu tutaj Optional czy CompletableFuture z Java8 (ale an szczęście są konwersje do javaslang i jest jakiś Future w Javaslang ale nie stosowałem)
Jakie to ma konsekwencje? Np w Scali gdy zrobię obliczenia na typie , który niesie ze sobą więcej informacji niż jest/nie ma to raz, że nie trace informacji o typie a dwa to, że nawet w przypadku failu wiem co się stało.
val result: Try[Int] = for { i1 <- Try(1) i2 <- Try(1 / 0) i3 <- Try(3) } yield i1 + i2 + i3 //Failure(java.lang.ArithmeticException: / by zero)A Javie niestety w kontekście obydwu wymienionych punktów możemy rozłożyć ręce...
Iterator<Integer> result = For( Try.of(() -> 1), Try.of(() -> 1 / 0), Try.of(() -> 3)) .yield((i1, i2, i3) -> i1 + i2 + i3); //Iterator()Ale i tak to co możemy zrobić z biblioteką Javaslang jest o niebo lepsze niż z samą biblioteką standardową. A pod tym linkiem jest więcej ciekawych inicjatyw : 5 Essentials for a Java 8 Tech Stack
11 Maja będzie warsztat o efektach ubocznych w Javie to będzie można sobie poćwiczyć : Warsztaty Java8 - Funkcje i Efekty Uboczne
Brak komentarzy:
Prześlij komentarz