Odpowiedzi po Confiturze

Lipiec 7th, 2016

W sumie odpowiedź na pytanie o biblioteki, które trzeba mieć w swoim arsenale, jeżeli ruszamy do boju przeciwko Javie 8 🙂

Na podstawie tego tekstu plus moje rozszerzenia:

  • Google Guava – Nie tylko kolekcje, ale też ESB, net, czy grafy (o tym nie pisałem). Tam jest dużo różnych ciekawych rzeczy ponad kolekcjami.
  • Lombok – generatory kodu, obiekty niezmienne itp. Robienie sobie dobrze na poziomie objętości kodu. Przy czym trzeba uważać, bo dookoła śmiga też JPA czy Spring ze swoimi proxy i aspektami.
  • pCollections – kolekcje, które czasami się przydają. Nie zastąpią tych z API, ale czasami potrzebujemy specyficznych funkcjonalności czy też niezmienności. To jest jakiś pomysł.
  • Javaslang – różne elementy funkcyjne, dość ciekawe strumienie i duża naturalność w pisaniu. Przy czym z tyłu głowy pytanie o wydajność… ale to jest Java 😉
  • JOOQ/JOOL – biblioteka, która ma podobne zadanie co pCollection. Generalnie czasami potrzebujemy jakiegoś ekstra API dla strumieni. Tu je zapewne znajdziemy.

Miła pani, która o to zapytała – kto ty?

Pytanie od Tomka Nurkiewicza o użycie parallel streamów. Wersja krótka – wątki potrzebują RAMu, którego nie mogliśmy zapewnić. Wersja trochę bardziej rozbudowana. „Optymalizacje” zrównoleglające mają taki dziwny „defekt”, że JVM próbuje alokować pamięć dla każdego wątku, ale nie swapując pamięci wątków nieaktywnych. Inaczej mówiąc, jeżeli chcemy za alokować np. 500MB pamięci dla każdego z 10 wątków (zakładając, że możemy puścić 10 wątków na raz) to potrzebujemy 5GB i jeżeli damy maszynie 2GB to się wyjebie (albo nie wiem jak to skonfigurować). Dlatego też trzeba delikatnie z mechanizmami wielowątkowymi.

Tyle na dziś.

10 lat Confitury i to już jest koniec…

Lipiec 4th, 2016

Długie dni mają to do siebie, że są długie. Start z mieszkania przed 6 i już o 11 byłem na miejscu, czyli w centrum konferencyjnym na Bobrowieckiej. Tam gdzie była pierwsza Confitura (2011), pod nazwą Confitura właśnie. Tym razem dane mi było obejrzeć tylko dwie prezentacje, ale mogłem poprowadzić trzecią 🙂 Cieszę się, że głosowaliście i zapewniliście mi tę możliwość.

Concurrency in Java – Mateusz Kaczmarek

Myśl przewodnia tej prezentacji i coś, o czym warto pamiętać. Nasz software nie żyje w próżni. JVM, system, krzem to wszystko należy brać pod uwagę, gdy piszemy kod współbieżny. Całkiem fajna prezentacja, rzeczowa i pozostawiająca pewien niedosyt. Warto było posłuchać.

Kotlin, why? – Paweł Byszewski

Paweł na przykładzie z życia pokazał, jakie są przewagi Kotlina nad „czystą” Javą. Całkiem fajna prezentacja, w której prawie nic nie zabrakło. Z drugiej strony mam wrażenie, że to „prawie”, to całkiem duży zakres materiału. Szczególnie że pytania z sali dotyczyły też składni. Z drugiej strony mając mało czasu, trzeba coś ciąć. Na pewno prezentacja ta pokazała mi, o jakich rzeczach warto wspominać, gdy omawiamy Kotlina, bo była prowadzona z punktu widzenia programisty androidowego.

To już jest koniec

Całe słuchanie ciężkich brzmień w moim wypadku zaczęło się od jakiejś kasety ze składanką Black Sabbath. Wcześniej było oczywiście Deep Purple, Pink Floyd czy Queen, ale to nie było jeszcze to. Niedługo później poznałem ból zakupu oryginalnych płyt… Paranoid kosztowało jakieś 69PLN… cóż. Wakacje upłynęły mi pod znakiem Electric Funeral, War Pigs i Fallouta 🙂

Koncert Black Sabbath był świetny. Jednak zawsze po tego typu wydarzeniach pozostaje pewien niedosyt. Poczucie, że mogli zagrać jeszcze jeden numer. Jednak i tak było świetnie. Szkoda, że był to ostatni koncert i nie będzie już okazji.

Chrzan z keczupem – własny interceptor Wasabi w Kotlinie

Czerwiec 29th, 2016

Tydzień odpoczynku starczy. Można wrócić do pisania 🙂 Dziś zaimplementujemy własny interceptor w frameworku Wasabi.

Czym jest Wasabi?

Wasabi to framework HTTP napisany w Kotlinie. Pozwala na tworzenie aplikacji wykorzystujących protokół http jako warstwy komunikacji. Posiada wiele elementów, które pozwalają go kwalifikować jako narzędzie REST, ale nie jest na pewno frameworkiem REST. Można tworzyć rozwiązania zorientowane na zasoby, ale nie to jest głównym zadaniem.

Cały mechanizm działania opiera się o dobrze znane elementy takie jak routing, kanały (channels), metody HTTP. Całość jest postawiona na nettym.

Czym są interceptory?

Jak sama nazwa wskazuje, służą one do przechwytywania żądań, które przychodzą do serwera. Następnie wykonywana jest pewna logika związana z żądaniem i wynik jest zwracany jako odpowiedź serwera, następuje przerwanie przetwarzania, albo przesyłany do dalszej obróbki. Można o nich myśleć jak o filtrach znanych z aplikacji webowych. Choć są trochę bogatsze, jeśli chodzi o możliwości.

Jednym z przypadków użycia interceptora jest obsługa statycznych zasobów takich jak statyczne, z punktu widzenia serwera, strony html, pliki js, css, obrazki. Innym jest obsługa nagłówków, autoryzacji, negocjacja dodatkowych parametrów połączenia itp.

Nasz mały interceptor

Wasabi ma na pokładzie klasę StaticFileInterceptor, która pozwala na obsługę statycznych zasobów. Ma on jednak małą wadę w postaci nie możności obsługi domyślnego pliku dla katalogu. Inaczej mówiąc, jeżeli żądanie wskazuje na zasób, który jest katalogiem to dostajemy 404, o ile gdzieś dalej nie leży mapowanie pasujące do wskazanego zasobu, a nie tak jak w przypadku serwerów www plik index.XXX.

Zgłosiłem odpowiednią poprawkę, która została już wdrożona, ale chciałbym omówić, jak do niej doszedłem.

Najpierw kod oryginalnego interceptora:

Listing 1. StaticFileInterceptor stan wyjściowy

public class StaticFileInterceptor(val folder: String): Interceptor() {
    override fun intercept(request: Request, response: Response): Boolean {
        var executeNext = false
        if (request.method == HttpMethod.GET) {
            val fullPath = "${folder}${request.uri}"
            val file = File(fullPath)
            if (file.exists() && file.isFile()) {
                response.setFileResponseHeaders(fullPath)
            } else {
                executeNext = true
            }
        } else {
            executeNext = true
        }
        return executeNext
    }
}

public fun AppServer.serveStaticFilesFromFolder(folder: String) {
    val staticInterceptor = StaticFileInterceptor(folder)
    intercept(staticInterceptor)
}

Mamy tu do czynienia z dwoma elementami. Pierwszy z nich, to implementacja interfejsu Interceptor. Metoda intercept kieruje dalszym przetwarzaniem żądania w następujący sposób. Jeżeli zwróci true, to żądanie jest przekazywane do mechanizmu routingu. Jeżeli zwróci false, to żądanie jest kończone i odsyłane, tak jak jest, co oznacza, że obiekt Response jest przekazywany do mechanizmu zamieniającego go na odpowiedni obiekt z nettiego, który dalej już robi robotę sieciową. Drugim elementem jest funkcja rozszerzająca, która jest dodana do AppServer i obudowuje mechanizm tworzenie i dodawania interceptora.

Wadę tego rozwiązania już opisałem wyżej. Przejdźmy więc do naszej implementacji, która jest trochę inna niż ta, która poszła do repozytorium, ponieważ znacznie intensywniej wykorzystuje wyrażenie when, które nie wszyscy lubią.

Listing 2. Nasz interceptor

class CustomStaticFileInterceptor(val folder: String, val useDefaultFile: Boolean = false, val defaultFile: String = "index.html") : Interceptor() {

    private fun existingDir(path: String): Boolean {
        val file = File(path)
        return file.exists() && file.isDirectory
    }

    private fun existingFile(path: String): Boolean {
        val file = File(path)
        return file.exists() && file.isFile
    }

    override fun intercept(request: Request, response: Response): Boolean {
        return when (request.method) {
            HttpMethod.GET -> {
                val fullPath = "${folder}${request.uri}"
                when {
                    existingFile(fullPath) -> {
                        response.setFileResponseHeaders(fullPath); false
                    }
                    existingDir(fullPath) && useDefaultFile -> {
                        response.setFileResponseHeaders("${fullPath}/${defaultFile}"); false
                    }
                    else -> true
                }
            }
            else -> true
        }
    }
}

fun AppServer.serveStaticFiles(folder: String, useDefaultFile: Boolean = false, defaultFile: String = "index.html") {
    val staticInterceptor = CustomStaticFileInterceptor(folder, useDefaultFile, defaultFile)
    this.intercept(staticInterceptor)
}

W tej wersji mamy kilka drobnych zmian. Po pierwsze dodałem dwa pola useDefaultFile oraz defaultFile, które mają domyślne wartości. Pozwoli nam to zachować wsteczną kompatybilność API. Następnie dopasowujemy warunki działania interceptora. Jeżeli mamy do czynienia z żądaniem innym niż GET, to zwracamy true, co pozwala na kontynuowanie przetwarzania (ostatni else). Jeżeli jednak mamy do czynienia z żądaniem GET, to mamy trzy możliwości. Pierwsza żądanie dotyczy istniejącego pliku, wtedy budujemy odpowiedź i zwracamy false. Druga to sytuacja, gdy żądanie dotyczy katalogu i mamy włączoną obsługę plików domyślnych. Wtedy budujemy odpowiedź z domyślnym plikiem, bez sprawdzania, czy plik istnieje – jego brak to błąd konfiguracji i nas nie obchodzi, i zwracamy false. Trzecia sytuacja reprezentowana przez else, to żądanie katalogu przy wyłączonej obsłudze plików. Zwracamy true i tyle.

W celu zachowania spójności musimy też dodać funkcję do AppServer. W zgłoszonej poprawce zmieniamy istniejącą klasę, a zatem, zamiast dodawać funkcję, zmieniamy ją. Ostatnim krokiem jest likwidacja powtórzeń na poziomie wartości domyślnych. Wystarczy je wyekstrahować do prywatnych stałych w pliku:

Listing 3. Ekstrakcja stałych i nagłówki

private val DEFAULT_USE_DEFAULT_FILE = false
private val DEFAULT_DEFAULT_FILE = "index.html"

class CustomStaticFileInterceptor(val folder: String, val useDefaultFile: Boolean = DEFAULT_USE_DEFAULT_FILE, val defaultFile: String = DEFAULT_DEFAULT_FILE) : Interceptor()
fun AppServer.serveStaticFiles(folder: String, useDefaultFile: Boolean = DEFAULT_USE_DEFAULT_FILE, defaultFile: String = DEFAULT_DEFAULT_FILE)

Podsumowanie

Wasabi framework jest bardzo młodym rozwiązaniem, ale świetnie nadaje się jako miejsce gdzie połączyć naukę języka z rozwojem rzeczywistego oprogramowania. Dzisiejszy przykład pokazuje jak łatwo w kotlinie jest stworzyć rozszerzenie do istniejącego rozwiązania, ponieważ język wymusza na twórcach elastyczność w tworzeniu kodu.

Szkolenie z Kotlina runda druga

Czerwiec 24th, 2016

Robimy powtórkę, bo zainteresowanie 🙂

REJESTREACJA

Podsumowanie I edycji

Mam nadzieję, że uczestnicy są zadowoleni. Co prawda trzy godziny to trochę za mało czasu. Forma ćwiczeń po przetestowaniu jej w boju też nie spełniła wszystkich moich oczekiwań, ale było nieźle. Zatem druga grupa będzie miała ten sam materiał, ale w trochę innej formie. Nie będą to duże różnice. Poprawimy kilka drobnostek, które wyszły w praniu.

Zwiększyliśmy też limit miejsc do 30 (jak to piszę to może jeszcze nie być tej informacji na stronie). A teraz szczegóły.

Gdzie?

Wrocław, pl. Konstytucji 3 Maja 3, budynek Silver Tower 13 piętro (zajebisty widok na Wrocław):

Wejście od ulicy Dąbrowskiego, drzwi obok krasnoludka.

Kiedy?

30 czerwca startujemy o 17:30 od czegoś na ciepło. cały czas będą dostępne przekąski na zimno i kawa na zimno z G Coffee Company.

Co i jak?

Będzie to wprowadzenie do Kotlina. Dlatego potrzebujecie własne laptopy z zainstalowanym:

  • Ulubionym IDE
  • Klientem git
  • Gradle
  • Javą 1.8

Całość potrwa około 3 godzin. Zakończenie planowane jest na 21. Później możemy pójść na piwo 🙂

REJESTREACJA

Już lato…

Czerwiec 21st, 2016

Szybko minęło. W sumie 96 wpisów w ramach blogowej wiosny… Gdy zabierałem się za ten cykl, to nie zauważyłem, że będzie to około 10% wszystkich wpisów na blogu. To było dużo pracy. Były święta i długie weekendy. Jeździliśmy po Polsce i zwiedzaliśmy. Czasami trzeba było „wyprodukować” kilka tekstów w przeciągu jednego dnia. Jednak udało się.

Co dalej?

Wiele razy słyszałem, że szata graficzna bloga jest zła. Mam tego świadomość i plany, by coś z tym zrobić. Może przez wakacje uda się jakoś rozwiązać ten problem. Łatwo nie będzie, bo treść jest specyficzna, co czyni układ graficzny trudnym do zaprojektowania.

Inna sprawa to korekta starszych wpisów. Szczególnie pod kątem poprawności listingów, ich formatowania i organizacji. Tu też czeka mnie dużo pracy.

Nadal otwartym tematem jest napisanie kilku pluginów do obsługi listingów. Szczególnie jeśli chodzi o wstawianie kodu do treści, bo to kuleje najbardziej.

Co teraz?

Zapewne kilka dni odpoczynku od pisania. Muszę przygotować kurs Kotlina, którego wersja skrócona już pojutrze.

uf… dało się 🙂

Szkolenie z Kotlina

Czerwiec 20th, 2016

Małe, 3 godzinne szkolenie z Kotlina już w ten czwartek 🙂

REJESTREACJA

Gdzie?

Wrocław, pl. Konstytucji 3 Maja 3, budynek Silver Tower 13 piętro 13 piętro (zajebisty widok na Wrocław):

Wejście od ulicy Dąbrowskiego, drzwi obok krasnoludka.

Kiedy?

23 czerwca (i później zapewne będzie powtórka) startujemy o 17:30 od lunchu, zatem jak chcesz coś wszamać, to trzeba być punktualnie.

Co i jak?

Będzie to wprowadzenie do Kotlina. Dlatego potrzebujecie własne laptopy z zainstalowanym:

  • Ulubionym IDE
  • Klientem git
  • Gradle
  • Javą 1.8

Całość potrwa około 3 godzin. Zakończenie planowane jest na 21. Później możemy pójść na piwo 🙂

Ogólne założenie jest takie, że zaimplementujemy rozwiązania kilku problemów, tak by poznać język.

REJESTREACJA

Pattern matching w Javie z Javaslang IV

Czerwiec 20th, 2016

Poprzednie części:

Początkowo chciałem włączyć temat tworzenia własnych wzorców do poprzedniego wpisu. Jednak ze względu na konieczność zrobienia kilku rzeczy, bez których będzie nam ciężko, postanowiłem wyłączyć ten temat do osobnego wpisu.

Dodatkowe zależności

Samodzielne pisanie wzorców jest uciążliwe i w sumie mało przyjemne. Javaslang jest jednak oparty o generatory, a zatem pozwólmy robić im ich robotę. W tym celu musimy dodać jedną zależność w naszym pliku build.gradle:

Listing 1. Konfiguracja projektu

apply plugin: 'java'

repositories {
    jcenter()
    mavenCentral()
}

sourceSets {
    generated {
        java {
            srcDirs = ['src/main/java']
        }
    }
}

configurations {
    patternMatch
}

dependencies {

    compile 'org.slf4j:slf4j-api:1.7.21'
    compile('io.javaslang:javaslang:2.0.2')
    patternMatch 'io.javaslang:javaslang-match:2.0.2'

    testCompile 'junit:junit:4.12'
}

task generatePatterns(type: JavaCompile, group: 'build', description: 'Generates patterns classes') {

    source = sourceSets.main.java 
    classpath = configurations.compile + configurations.patternMatch
    options.compilerArgs = [
            "-proc:only",
            "-processor", "javaslang.match.PatternsProcessor"
    ]
    destinationDir = sourceSets.generated.java.srcDirs.iterator().next()
}

compileJava {
    dependsOn generatePatterns

    options.compilerArgs =[
            "-proc:none"
    ]
}

compileGeneratedJava {
    dependsOn generatePatterns
    options.warnings = false
    classpath += sourceSets.main.runtimeClasspath
}

Biblioteka javaslang-match zawiera procesor adnotacji, który wygeneruje nam odpowiednie klasy. Jednak by prawidłowo działał, należy:

  • Stworzyć dodatkowy task, który zajmie się generowaniem klas.
  • Wyłączyć procesory adnotacji w głównym cyklu kompilacji.

Pierwsze robimy poprzez dodanie zadania generatePatterns oraz konfiguracji patternMatch. Drugie za pomocą dodania argumentu -proc:none do wywołania kompilatora. Powyższa konfiguracja to kombinacją braku możliwości konfiguracji generatora i mojej niewiedzy w zakresie konfigurowania Gradle.

Model

Tu sprawa jest prosta. Będziemy używać klasy User, która zawiera trzy pola:

Listing 2. Klasa User

public class User {

    private String name;
    private String email;
    private String role;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }
}

Ot zwykłe POJO. Równie dobrze możemy użyć data class z Kotlina. Jednak jest jeden warunek. Jeżeli nie chcemy samodzielnie kompilować Javaslang, to musimy ograniczyć się do maksymalnie ośmiu pól w dekompozycji obiektu. Tyle właśnie wynosi największa pojemność krotki w Javaslang.

Własny wzorzec

Tworzenie własnego wzorca rządzi się prostymi zasadami. Po pierwsze musimy stworzyć klasę, która będzie zawierać wzorce. Oznaczymy ją adnotacją @Patterns.

Listing 3. Klasa Users

@Patterns
public class Users {
 
}
T’n’T z nazwami

Generator utworzy klasę UsersPatterns. Dlaczego nie możemy utworzyć klasy User? Jest bug w Javaslang, który powoduje, że generator nie używa pełnych nazw klas wraz z pakietami. W efekcie w pewnym momencie mamy sytuację jak poniżej:

Listing 4. Błędnie wygenerowana klasa

package pl.koziolekweb.javaslang.patternmatching.patterns;

import javaslang.API.Match.Pattern;
import javaslang.API.Match.Pattern3;
import pl.koziolekweb.javaslang.patternmatching.User;

// GENERATED BY JAVASLANG <<>> derived from pl.koziolekweb.javaslang.patternmatching.patterns.User

public final class UserPatterns {

    private UserPatterns() {
    }

    public static <_1 extends String, _2 extends String, _3 extends String> Pattern3<User, _1, _2, _3> User(Pattern<_1, ?> p1, Pattern<_2, ?> p2, Pattern<_3, ?> p3) {
        return Pattern3.of(User.class, p1, p2, p3, User::User);
    }

}

Problemem jest zapis User::User, który odwołuje się do naszej klasy ze wzorcami. Jednocześnie mamy zaimportowaną klasę pl.koziolekweb.javaslang.patternmatching.User, co prowadzi do konfliktu nazw. W efekcie mamy błąd kompilacji…

Zgłosiłem buga. Zobaczymy co z nim zrobią. Rozwiązali go.

Dekonstrukcja

Kolejnym krokiem jest dodanie metody, która będzie dekonstruować obiekt User do Tuple3. Z dekonstrukcją na poziomie semantyki języka mieliśmy już do czynienia w Kotlinie. Choć brzmi to groźnie, to sama implementacja jest banalnie prosta:

Listing 5. Metoda User służy do dekonstrukcji

@Patterns
public class Users {
 
    @Unapply
    static Tuple3<String, String, String> User(pl.User user) {
        return Tuple.of(user.getName(), user.getEmail(), user.getRole());
    }

}

Metoda musi mieć adnotację @Unapply i musi być statyczna. Możemy używać naszego wzorca:

Listing 6. Użycie User w dopasowaniu

public void custom() {
    User user = new User("Jan Kowalski", "jan@kowalski", "Jan");

    System.out.println(
            Match(user).of(
                    Case(User($(), $(), $("Jan")), "OK - Jan"),
                    Case(User($(), $(), $("Emil")), "OK - Emil")
            )
    );
}

W efekcie mamy możliwość dopasowania obiektu do wzorca według samodzielnie zdefiniowanych reguł.

Podsumowanie

Tworzenie własnych wzorców i reguł dopasowania nie jest trudne. Najwięcej czasu zajęło mi odpowiednie skonfigurowanie gradle tak by generowane klasy były widoczne w projekcie. To jest jakaś porażka niestety.

Pattern matching w Javie z Javaslang III

Czerwiec 19th, 2016

Poprzednie części:

Dopasowania wartości z pierwszej części oraz predykatów z drugiej, to nie jedyne możliwości, jakie daje nam Javaslang. Dziś przyjrzymy się wzorcom, czyli specjalnym obiektom, które opisują proces dekonstrukcji danego obiektu tak, by można było go wykorzystać w dopasowaniu. Poznaliśmy już wzorce, które mają znaczenie „każdy pasuje” – $(), oraz jest „równy w sensie equlas” – $(X). Standardowe API daje nam jednak kilka innych możliwości.

Sukces czy porażka

Mamy sobie kontener Try, o którym innym razem. Może mieć dwa stany – sukces (Success) i porażka (Fail). Jest to idealny kandydat do obsługi przez mechanizm dopasowań:

Listing 1. Dopasowanie Try.

public void successOrFailure() {
    Try<Integer> success = Try.of(() -> 1);
    Try<Integer> fail = Try.of(() -> {
        throw new Exception("Ni huhu");
    });

    System.out.println(
            Match(success).of(
                    Case(Success($()), "OK"),
                    Case(Failure($()), "NOK")
            )
    );

    System.out.println(
            Match(fail).of(
                    Case(Success($()), "OK"),
                    Case(Failure($()), "NOK")
            )
    );
}

Jako że Try ma tylko dwa stany to możemy pominąć warunek związany z dopasowaniem $() na końcu. Oczywiście możemy udoskonalić nasz kod, stosując bardziej precyzyjne dopasowania:

Listing 2. Dopasowanie Try z wykorzystaniem wzorców wewnętrznych.

public void successOrFailure() {
    Try<Integer> success = Try.of(() -> 1);
    Try<Integer> fail = Try.of(() -> {
        throw new IllegalArgumentException("Ni huhu");
    });

    System.out.println(
            Match(success).of(
                    Case(Success($(1)), "1 is OK"),
                    Case(Success($(i -> i == 2 )), "2 is OK too"),
                    Case(Failure($()), "NOK")
            )
    );

    System.out.println(
            Match(fail).of(
                    Case(Success($()), "OK"),
                    Case(Failure($(instanceOf(IllegalArgumentException.class))), "NOK - IAE"),
                    Case(Failure($()), "NOK")
            )
    );
}

Dopasowania krotek

Czyli mięsko, o które mnie pytano. Skoro można tworzyć wzorce dla takiego Try, to co stanie się, gdy zaczniemy używać wzorców dla Tuple?

Listing 3. Dopasowanie krotki

public void tuplePattern() {
    Tuple3<String, String, String> of = Tuple.of("Ala", "nie ma", "kota");

    System.out.println(
            Match(of).of(
                    Case(Tuple3($(), $("ma"), $()), "Wiedźma!"),
                    Case(Tuple3($(), $("nie ma"), $()), "Pożeraczka kotów!")
            )
    );
}

W powyższym przykładzie mamy dodatkowo ukryte dopasowanie typów. Wszystkie wzorce muszą być typu Pattern.Tuple3. Technicznie wzorzec jest funkcją, która zwraca typ zgodny z typem obiektu dopasowywanego. Dlatego nie może być to zwykłe Tuple3. Jednocześnie nie możemy w żaden sposób pracować z krotkami o różnej liczności. Oczywiście rozwiązaniem jest użycie typu Tuple dla zmiennej of.

Listing 4. Dopasowanie dowolnej krotki

public void tuplePattern() {
    Tuple3<String, String, String> of = Tuple.of("Ala", "nie ma", "kota");

    System.out.println(
            Match(of).of(
                    Case(Tuple3($(), $("ma"), $()), "Wiedźma!"),
                    Case(Tuple4($(), $("nie"), $("ma"), $()), "Pożeraczka kotów!!!"),
                    Case(Tuple3($(), $("nie ma"), $()), "Pożeraczka kotów!")
            )
    );
}

Oczywiście przekazanie krotki, która do niczego nie pasuje spowoduje wyjątek.

Podsumowanie

Mechanizm dopasowania wzorców jest bardzo elastyczny i umożliwia nam zupełnie inne podejście do tworzenia kodu. Oznacza to, że lepiej możemy dzielić logikę i dane. W połączeniu z innymi elementami mamy naprawdę silne narzędzie.

Pattern matching w Javie z Javaslang II

Czerwiec 18th, 2016

Poprzednie części:

Dopasowanie wzorców do wartości jest w sumie proste. Podobny efekt można uzyskać stosując mapy. Jednak Javaslang udostępnia też API, w którym dopasowanie oparte jest o predykaty. Przy czym są to predykaty biblioteki, a nie Javy 8.

Listing 1. Dopasowanie z wykorzystaniem predykatu

public void predicates() {
    Integer i = 0;
    String on = Match(i).of(
            Case(x -> x == 1, "Jeden"),
            Case(x -> x == 2, "Dwa"),
            Case($(), "?")
    );
    System.out.println(on);
}

Predykaty z API

Oczywiście API Javaslang udostępnia nam cały zestaw predykatów, których możemy użyć „od ręki”:

Listing 2. Przykładowe predykaty z API

public void predicates2(Object i) {
    String on = Match(i).of(
            Case(is(1), "Jeden"),
            Case(isIn(2, 3), "Dwa albo 3"),
            Case(anyOf(is(4), noneOf(is(5), is(6))), "4 lub nie (5 lub 6)"),
            Case(instanceOf(String.class), "Jakiś napis"),
            Case($(), "?")
    );
    System.out.println(on);
}

Do wyboru, do koloru. Predykaty można łączyć na wiele sposobów. anyOf, noneOf to tylko dwa z kilku dostępnych.

Wartości zwracane

Dotychczas patrzyliśmy tylko na to, jak można zdefiniować dopasowanie. Wartość zwracana z warunku była na sztywno zapisana w kodzie. Zamiast tego można użyć funkcji i Supplierów z API Javy 8:

Listing 3. Przykładowe zwracanie wartości

public void returnig() {
    Integer i = 1;
    String on = Match(i).of(
            Case($(1), v -> v + ""),
            Case($(2), () -> "Dwa"),
            Case($(), "?")
    );
    System.out.println(on);
}

Efektywnie Match zachowuje się jak wyrażenie i musi zwrócić wartość. Co w przypadku gdy zamiast zwracania wartości chcemy wykonać jakąś czynność? W tym przypadku musimy użyć metody pomocniczej run:

Listing 4. Użycie run w celu uruchomienia kodu

public void running() {
    Integer i = 1;
    Void of = Match(i).of(
            Case($(1), () -> run(System.out::println)),
            Case($(2), () -> run(System.out::println)),
            Case($(), () -> null)
    );
}

Krytycznym wymaganiem jest opakowanie run w Supplier. W przeciwnym wypadku zostanie on uruchomiony przed procedurą dopasowania. Dlaczego? Ponieważ ewaluacja parametrów w Javie jest zachłanna.

Podsumowanie

Jak widać, dopasowanie wzorców w wersji Javaslang jest dobrze rozwinięte i pozwala na uzyskanie wielu rozwiązań, które są znane z języków, gdzie mechanizm ten jest częścią samego języka.

Pattern matching w Javie z Javaslang I

Czerwiec 17th, 2016

Gdy kilka dni temu wrzuciłem wpis o tuplach, prawie od razu pojawiły się pytania:

hmmm…. a do czego takie dziwne konstrukcje ? taka sztuka dla sztuki

Samodzielnie krotki rzeczywiście są mało przydatne. Co prawda czasami warto ich używać np. gdy potrzebujemy POJO, a nie można go stworzyć albo chcemy sobie zapewnić kolejność typów w strukturze. Jednak znacznie więcej możliwości dają nam krotki w połączeniu z dopasowaniem wzorców. Dziś zaczniemy omawiać ten mechanizm i jego implementację w Javaslang. Nie jest on rozwiązaniem natywnym, jak na przykład w Kotlinie, ale i tak daje on dużo możliwości.

Dopasowanie dla wartości

To co ułatwi nam życie jest statyczny import całego javaslang.API..

Jeżeli chcemy dopasować jakąś wartość do wzorca by uzyskać np. odpowiedni obiekt możemy użyć ifologii. Proste, łatwe i zrozumiałe, prawda?

Listing 1. Ifologia w praktyce

public void ifology() {
    Integer i = 0;
    String on;
    if (i == 1) {
        on = "Jeden";
    } else if (i == 2) {
        on = "Dwa";
    } else {
        on = "?";
    }
    System.out.println(on);
}

Raptem trzy proste warunki i bardzo dużo kodu. Co będzie w przypadku gdy przetwarzamy plik binarny, blok po bloku i chcemy odpowiednio interpretować nagłówki bloków? No delikatnie mówiąc przejebane. Zamiast tego możemy napisać tak:

Listing 2. Pattern matching dla wartości

public void introduction() {
    Integer i = 0;
    String on = Match(i).of(
            Case($(1), "Jeden"),
            Case($(2), "Dwa"),
            Case($(), "?")
    );
    System.out.println(on);
}

Przy dużej liczbie warunków kod nadal będzie długi, ale znacznie bardziej czytelny. Pary dopasowanie – wartość pozwalają na lepsze zrozumienie kodu.

// offtopic

Zapewne wiele osób powie, że nie warto kombinować, bo ify są łatwiejsze w zrozumieniu. Nie do końca. Problemem jest nieznajomość API, idiomów i konstrukcji z biblioteki. Jeżeli je poznamy, to okazuje się, iż czytanie kodu napisanego tak jak na listingu 2 jest naturalne. Równie dobrze można zarzucić komuś, że pisze niezrozumiały kod w Javie, bo w C# pisze się zupełnie inaczej. Szczytem tego typu dyskusji są te o notacji.

// koniec offtopicu

Dopasowanie tworzymy za pomocą metody $(wartość). Dopasowania są sprawdzane po kolei i pierwsze pasujące zwraca wynik. Metoda $() jest dopasowaniem w rodzaju „dla każdego”. Jeżeli jej nie umieścimy, to otrzymamy błąd MatchError:

Listing 3. Brak dopasowania domyślnego prowadzi do błędu

public void introduction2() {
    Integer i = 0;
    String on = Match(i).of(
            Case($(1), "Jeden"),
            Case($(2), "Dwa")
    );
    System.out.println(on); // nie wyświetli się
}

Jeżeli wiemy, że może dojść do takiej sytuacji, a nie mamy reguł dla domyślnego dopasowania, to możemy z korzystać z opcji:

Listing 4. Dopasowanie i Option

public void introduction3() {
    Integer i = 0;
    Option<String> on = Match(i).option(
            Case($(1), "Jeden"),
            Case($(2), "Dwa")
    );
    System.out.println(on);
}

Wynikiem będzie None. Javaslang używa własnych kontenerów wartości. Warto o tym pamiętać.

Podsumowanie

Mając dopasowania dla wartości, możemy już znacznie ulepszyć nasz kod. Łącząc je z leniwą ewaluacją wartości, można uzyskać całkiem ciekawe rozwiązania niektórych problemów.


Translate »