wtorek, 3 maja 2016

For comprehension ale że w Javie

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