niedziela, 12 listopada 2017

Functional Calisthenics z Kotlinem na GDCR 2017

Code Retreat - a zwłaszcza GLOBAL - to doskonała okazja do nauki programowania, zerwania z nieskutecznymi nawykami i odnalezienie nowych podejść do rozwiązywania problemów. A, że to jest to spotkanie cykliczne to akurat w tym miejscu wykorzystam starą metodę DRY i a TY będziesz dispatcherem/dispatcherką wywołania. Także wybierz sobie wstęp z jednego z poniższych artykułów.

  1. Motywacja do GDCR 2013
  2. Wnioski po GDCR 2013
  3. Kilka przykładów użycia mechanizmów FP ze scali do Game of Life
  4. Po GDCR 2014
  5. przed GDCR 2015 - tutaj w ogóle jakiś Haskell jest :D
  6. Po GDCR 2016 - przymiarki do funkcyjnego CR
  7. Wrażenia po GDCR 2016, którego nie było i tym razem trochę lepszy Haskell

Skoro już trochę historii za nami to wróćmy na chwilkę do ograniczeń. Jednym z popularniejszych na CR jest ograniczenie zwane object calisthenics, które jest nakierowane na poprawienie umiejętności pisania poprawnego obiektowego kodu. I od razu musimy sobie wyjaśnić jedną rzecz - jeśli z jakiegoś powodu nie podoba ci się programowanie obiektowe to masz jak najbardziej prawo je krytykować ale najpierw musisz zrozumieć o co w tym wszystkim chodzi. Jeśli dzień za dniem rzeźbisz klasy, które nazywają się ManagerCosTamImpl albo CosTamHelper to nie masz problemu z programowaniem obiektowym ale zwyczajnie piszesz nad wyraz złozony kod proceduralny w języku, który wymusza by ten kod proceduralny był w klasach - stąd te kolosy po 8000 linijek.

A wracając do ograniczeń to object calisthenics mają np. takie ćwiczenie "jedna kropka na linie kodu" i to ma pomóc pisać kod, który lepiej enkapsuluje dane w klasach bo trudniej o tzw. train wreck obj.get()A.get()B.setC(c). No i jako, że programowanie funkcyjne również zaczyna grać ważną role w twoim ulubionym języku to warto by podobne zasady wprowadzić dla FP.

W 2016 mieliśmy przerwę ale już w tym roku Code Retreat wraca na JUG Łódź. Spotkanie odbędzie się 18 listopada a zapisac można się tutaj -> Global Day of CR łódź Meetup. W tym roku będzie także zapewniona opieka nad dziećmi także można wpaść z rodziną.

No i by do tematu nauki podejść całościowo i rozszerzyć ćwiczenia o FP to tym razem najprawdopodobniej kilka ćwiczeń będzie zainspirowanych nowym zestawem functional calisthenics -> https://codurance.com/2017/10/12/functional-calisthenics/

No to jedziemy z koksem i obczajamy te zasady. Tym razem użyjemy języka, który zdobywa coraz większą popularność na JVM, ma dobre wsparcie w IDE bo robi go firma, która sama robi IDE a do tego jest wciąż bezpiecznie ubogi tak, ze korporacje nie powinny nim sobie zrobić krzywdy - mowa o Kotlinie.


Zasada 1 : Nazywaj wszystko i naucz się rozpoznawać wzorce w kodzie.

"All functions need to be named. Which means that you shouldn't use lambdas or anonymous functions. Also, name all your variables as per standard clean code rules."

Jeśli musisz dać czemuś nazwę to istnieje duża szansa, ze wiesz co ten fragment musi robić. No i teraz jeśli zamiast używać anonimowych funkcji zaczniesz je definiować i nazywać to może zauważysz szansę na parametryzacje i kompozycje. Ale aby to miało ręce i nogi najpierw musisz w ogóle zastosować podejście funkcyjne a tutaj moga pomóc bardziej fundamentalne ograniczenia jak np. brak zmiennych.

Co do implementacji to tutaj bardzo może się przydać nowy ficzer z kotlina 1.1 - aliasy typów.

//aliases since Kotlin 1.1
typealias Cell = Boolean
typealias Neighbours = Int

val LIVE_CELL:Cell = true
val DEAD_CELL:Cell = false


fun cellEvolution(c:Cell,n:Neighbours): Cell =
        when(c){
            LIVE_CELL -> if(n==2 || n==3) LIVE_CELL else DEAD_CELL
            DEAD_CELL -> if(n==3) LIVE_CELL else DEAD_CELL
        }

Powyżej pierwsza próba, w trakcie której próbowałem zadeklarować dwie stałe okreslające stan komórki DEAD oraz LIVE jakkolwiek kompilator (a przynajmniej plugin do Intellij) nie był w stanie wykryć iż wszystkie warunki w wywołaniu when zostały ogarnięte. Dlatego tez powstało drugie podejście - sealed class

sealed class Cell
object LiveCell :Cell()
object DeadCell :Cell()


fun cellEvolution(c:Cell,n:Neighbours): Cell =
        when(c){
            LiveCell -> if(n==2 || n==3) LiveCell else DeadCell
            DeadCell -> if(n==3) LiveCell else DeadCell
        }


Zasada 2 : Brak zmiennego stanu.

Dosłownie : "You shouldn't use any variable of any type that can mutate."

Najważniejsze w tym ćwiczeniu jest "zablokowanie" uczestnikom drogi w kierunku (nawet i poprawnego) programowania obiektowego gdyż w tym wypadku nie można owinąć zmiennego stanu instancją obiektu.

//THIS IS FORBIDDEN
class MutableCell(private var state:Boolean){
    fun evolve(n:Neighbours){
        //set new state here
    }
}

Pewne trudności moga sie pojawić kiedy będziemy chcieli zmienić stan planszy - no ale przecież zmiany nie są dozwolone!. Kotlin dosyć ładnie oddziela api służące do deklarowania i używania niemutowalnych kolekcji od mutowalnych odpowiedników. Aby transformować niemutowalne listy tworząc nowe niezależne wersje używamy funkcyjny wyższego rzędu jak map czy filter obecnych teraz nawet w Javie.

typealias Coordinates=Pair<Int,Int>
typealias CellPosition=Pair<Coordinates,Cell>
typealias Board = kotlin.collections.List<CellPosition>

val nextRound : (Board) -> Board = {board ->
    board.map { ??? }
}

No i zobacz czytelniku/czytelniczko jakim wygodnym pomysłem są aliasy w przykładzie powyżej.

Zasada 3 : Każdy argument na wejściu zwraca wynik

"There can not be an if without an else. Switches and pattern matching should always have all paths considered"

To jest dosyć ciekawa zasada bo dobrze oddaje naturę code retreat. Tak więc mamy proste założenie, którego uczestnicy mają się trzymać i obserwować wyniki. Jest to na początku znacznie prostsze aniżeli wchodzenie w rozważania teoretyczne o funkcjach częściowych. Każdy argument musi generować konkretny wynik - bez wyjątków - dosłownie!

Zasada 4 : Nie używaj zmiennych tymczasowych

Zasada 5 : Expressions, not statements

Te dwie zasady postanowiłem połączyć bo w moim odczuciu dotyczą podobnego tematu. Dodatkowo druga zostawiam po engliszu by nie popełnić błędów w tłumaczeniu.

"There shouldn't be any variable declared in the body of a function." oraz "All lines should be expressions. That's it, all lines should return a value."

Tutaj języki takie jak Kotlin czy Scala mogą pomóc bardzo, bardzo, bardzo bo tam wszystko zdaje się coś zwracać! A szczególnie if jest wygodny jak coś zwraca. W Javie zazwyczaj trzeba coś tam manipulować po bokach.

fun badEvolve(cell:Cell,neighbours:Int):Cell{
    var newCell:Cell?=null //val newCell:Cell would also work here but var shows problem better
    if(cell==LiveCell && neighbours in (2..3))  {
        newCell = LiveCell
    }
    else if(cell==LiveCell)  {
        newCell = DeadCell
    }
    else if(cell==DeadCell && neighbours==3) {
        newCell=LiveCell
    }
    else {
        newCell=DeadCell
    }

    return newCell
}

W Javie często deklarowane są zmienne tymczasowe (pamiętacie te czasy gdy 90% zmiennych w waszym godzinie nazywało się temp?) tylko po to by coś w nich na chwilę przypisać i od razu dać return. Wyjście? Niech if coś zwraca i niech to będzie wynik metody. No i w Kotlinie to dzieje sie naturalnie. W Javie niby jest tzw. "ternary operator" ale on chyba dlatego nazywa się ternary bo nie ma "if-else". Trochę peszek.

fun betterEvolve(cell:Cell,neighbours:Int):Cell{
    return if(cell==LiveCell && neighbours in (2..3)) LiveCell
    else if(cell==LiveCell) DeadCell
    else if(cell==DeadCell && neighbours==3) LiveCell
    else DeadCell
}

No i teraz najlepsze. Ponieważ jest to jedyna instrukcja funkcji to możemy wyrażenie uprościć jeszcze bardziej.

fun bestEvolve(cell:Cell,neighbours:Int):Cell =
        if(cell==LiveCell && neighbours in (2..3)) LiveCell
        else if(cell==LiveCell) DeadCell
        else if(cell==DeadCell && neighbours==3) LiveCell
        else DeadCell

Zasada 6 : Brak jawnej rekurencji

Przyznam, że tego punktu do nie rozumiem a to z faktu, że autorzy odnoszą się do jakichś konstrukcji z Clojure. Coś tam kiedyś czytałem o "loop/recur" ale nie będę udawał, że pamiętam. Tak czy inaczej w tym punkcie znajdziemy poradę by tę "jawną rekurencję" zastąpić map/reduce . No to tak będziemy starali się zrobić.

Zasada 7 : Generyczne bloki

"Try to use a much as possible generic types for your functions, outside of the boundaries of your application"

To jets dosyć interesujący i abstrakcyjny sam w sobie punk. Z jednej strony ile razy deklarowałeś listę kiedy kiedy Collecion lub Iterable w zupełności wystarczało? Z tym ograniczeniem będziesz musiał dokładnie uzasadnić czy typ, którego używasz nie jest zbyt konkretny co może związać ci ręce przy dalszych zmianach. Dodatkowo w Kotlinie ze względu na wykrywanie typów musisz się zastanowić czy pozostawić prace kompilatorowi, który pewnie znajdzie najbardziej konkretny typ czy też może zadecydować świadomie samemu.

Możesz zastosować podobne podejście do funkcji ale tutaj idzie to trochę w inną stronę. Czy obecna implementacja dla danego typu nie jest zbyt "wąska"? Klasycznym przykładem jest tutaj reduce które jest generalizacją dodawania, mnożenia i czego tam sobie zażyczymy.

fun  add(list:List<Int>) : Int =if(list.isEmpty()) 0 else list[0] + add(list.drop(1))
fun  multiply(list:List<Int>) : Int =if(list.isEmpty()) 1 else list[0] * multiply(list.drop(1))

fun <A> genericReduce(l:List<A>,neutral:A,reduction:(A,A)->A):A =
        if(l.isEmpty()) neutral else reduction(l[0], genericReduce(l.drop(1),neutral,reduction))

I zgodnie z oczekiwaniami

println(add(listOf(1,2,3,4,5)))
println(multiply(listOf(1,2,3,4,5)))

println(genericReduce(listOf(1,2,3,4,5),0,{a,b->a+b}))
println(genericReduce(listOf(1,2,3,4,5),1,{a,b->a*b}))

Zasada 8 : Efekty uboczne tylko na granicy systemu

"Side effects should only appear on the boundaries of your application. The guts of your application should have no side effects."

Tutaj po pierwsze musisz używać funkcji, które nie zmieniają żadnego stanu poza nimi żadnego println czy też modyfikowania mutowalnej tablicy zdefiniowanej poza funkcją. To ograniczenie może być jeszcze bardziej interesujące gdy założysz, że efekty na granicy systemu musza być reprezentowane przy pomocy odpowiednich typów.

W takich wypadkach możesz skorzystać z typu Optional dodanego do Javy8

typealias Coordinates=Pair<Int,Int>
typealias CellPosition=Pair<Coordinates,Cell>
typealias Board = kotlin.collections.List<CellPosition>

val initializeBoard:(Coordinates) -> Board = ...
val nextTurn: (Board) -> Board = ...


fun readInput():Optional<Coordinates> = ...

fun display(b:Board):Unit = ...

readInput().map(initializeBoard).map(nextTurn).ifPresent(::display)

W przykładzie powyżej możesz zobaczyć, że Kotlin całkiem wygodnie integruje Jawowe klasy. Optional służy tam do zasygnalizowania efektu ubocznego związanego z wprowadzaniem danych ze świata zewnętrznego - który jakby nie patrzyć jest trochę uboczny.

Bardziej zaawansowani uczestnicy mogą zechcieć skorzystać z również bardziej zaawansowanej biblioteki http://kategory.io/ i użyć IO Monad do wypisywania tekstu na konsolę http://kategory.io/docs/effects/io/ .

Zasada 9 : Nieskończone strumienie danych

W skrócie - nie możesz używać tablic ani kolekcji. Tam gdzie użyłbyś standardowej kolekcji musisz użyć strumieni/ nieskończonej sekwencji

Programowanie Funkcyjne kładzie ogromny nacisk na operowanie na wartościach bez mutowania stanu. Jeśli nie zmieniamy stanu i wartości są stałe to czy ważne jest kiedy operacje będą miały miejsce (stąd FP ułatwia operacje wielowątkowe)? No nie ma w związku z czym możemy pracować na tzw. leniwych kolekcjach które są niejako "opisane" ale nie istnieją puki nie trzeba.

Przykładowymi kandydatami do "strumieniowania" w grze życie są komórki - na z definicji nieskończonej planszy. Również gra sama w sobie jest nieskończoną kolekcją tur.

//here we are playing 10 turns
val init:Board = initializeBoard()
generateSequence(init,nextTurn).take(10).forEach(::display)

Ale zamiast 10 tur możemy chcieć grać do momentu kiedy na planszy mamy jakiekolwiek żywe komórki. Aby było bardziej interesująco przeskoczmy na chwile do Javy. Pierwszą rzeczą jaką chcemy sprawdzić to czy da się wykorzystać kod, ktory napisaliśmy w Kotlinie.

List<Pair<Pair<Integer, Integer>, Cell>> board = Life1Kt.initializeBoard();

Technicznie jest to możliwe ale praktycznie raczej nikomu nie będzie się chciało babrać z tak rozbudowanymi typami generycznymi. No dobrze to spróbujmy od początku :

Board init= ...;
Predicate<Board> hasLiveCells = ...;
UnaryOperator<Board> nextTurn = ...;
Consumer<Board> display=...;

//below iterate version possible from Java9
Stream.iterate(init,hasLiveCells,nextTurn).forEach(display);

//take while possible from Java9
Stream.iterate(init,nextTurn).takeWhile(hasLiveCells).forEach(display);

Zasada 10 : Tylko funkcje jedno parometrowe

"Each function should have a single parameter."

Według mnie to najważniejsze ograniczenie by dobrze zrozumieć siłę kompozycji funkcji. Gdy przekazujesz do metody całą paczkę parametrów możesz ją postrzegać jako wielki monolit. Lecz kiedy przesłanie kolejnych parametrów określa tak naprawdę innego rodzaju funkcje - wtedy musisz się zastanowić co z czego tworzysz. Dla przykładu funkcję findNeighbours możesz zaimplementować na dwa sposoby.

findNeighbours : (Coordinates) -> (Board) ->  Neighbours = ...

Czy widzisz różnicę? Czy lepiej mieć funkcje, która znajdzie sąsiadów dla przekazanych koordynat na z góry określonej planszy czy może wygodniej jest przekazywać planszę dla wcześniej orkeślonych koordynat?

Co do samego Kotlina to nie ma on tak wygodnego wsparcia dla curryingu jak scala (currying to własnie to, że przekazujesz parametr po parametrze) ale jak się trochę ogarnie składnie to też daje radę

val evolve : (Neighbours) -> (Cell) -> Cell = {ns->{c->c}}

Ewentualnie produkuj funkcję z metody

fun evolve2(ns:Neighbours) : (Cell) -> Cell = {c -> c}

Zasada X : Żadnych nulli

Ta zasada co prawda nie pojawia się w oryginalnym zestawie ale w programowaniu funkcyjnym null jest ... bez sensu. Kotlin ma ciekawe patenty na poziomie języka do radzenia sobie z nullami . Jeśli nie chcesz w nie wchodzić wtedy cóż, zawsze zostaje prawilne podejście z opakowywaniem każdego nullowego api jak map.get w Optional.

Podsumowanie

Podobnie jak chodząc na siłownię powtarzasz pewne ćwiczenia tak samo tutaj powtarzając cwiczenia nabierasz informatycznej siły. I analogicznie samo czytanie o ćwiczeniach niewiele ci da. Jeśli chcesz zwiększyć siłę programowania funkcyjnego ćwicz,ćwicz,ćwicz,ćwicz,ćwicz,ćwicz,ćwicz,ćwicz,ćwicz,ćwicz,ćwicz,ćwicz. Poszukaj spotkania code retreat w twojej okolicy lub podobnych warsztatów i nie opierdalaj się!

niedziela, 15 października 2017

Fajny nowy acz niepozorny operator z Javy 9

I nie chodzi o moduły a o nową ciekawą metodę Optional, która zwię się or i jest dostępna od Javy 9.

To skoro small talk mamy za sobą to dlaczego ona taka fajna?

Unia opcjonalnych typów w dynamicznych strukturach

Taka sytuacja. Masz jakiś kanał komunikacji i otrzymujesz przez ten kanał opcjonalną wartość jakiegoś typu. I teraz ten typ którego wartość jest opcjonalna może przyjąć jedną z na przykład 4 elementowego zbioru wartości gdzie każdy element może mieć różną liczbę różnych atrybutów. Na rysunku duże kółko to symbolika typu abstrakcyjnego a małe kółka to podtypy - i technicznie wszystko jest tak opcjonalne, że ani nie wiadomo czy jakaś wartość będzie przekazana a jeśli będzie to jaka to wartość będzie?

Gdy już mało mówiący opis abstrakcyjny mamy za sobą teraz będzie trochę konkretów w stylu retro.

Retro przykład

Żeby nie było, że tylko nowe nowe nowe to zerknijmy zatem na przykład pamiętający czasy monitorów monochromatycznych

Poniżej kawałek jakiejś tam definicji jednego z elementów w jakiejś tam specyfikacji. Nieważne. Ważny jest ten choice który sprawia, że tam w tym typie może być w sumie cokolwiek.

 Schema Definition

   <element name="X509Data" type="ds:X509DataType"/> 
   <complexType name="X509DataType">
     <sequence maxOccurs="unbounded">
       <choice>
         <element name="X509IssuerSerial" type="ds:X509IssuerSerialType"/>
         <element name="X509SKI" type="base64Binary"/>
         <element name="X509SubjectName" type="string"/>
         <element name="X509Certificate" type="base64Binary"/>
         <element name="X509CRL" type="base64Binary"/>
         <any namespace="##other" processContents="lax"/>
       </choice>
     </sequence>
   </complexType>

   <complexType name="X509IssuerSerialType"> 
     <sequence> 
       <element name="X509IssuerName" type="string"/> 
       <element name="X509SerialNumber" type="integer"/> 
     </sequence>
   </complexType>

Żródło : https://www.w3.org/TR/xmldsig-core/ <--- jakiś tam losowy standard sprzed 10 lat wybrany losowo na potrzeby artykułu.

No i są takie patenty w stylu JAX-WS, które to przewalą na kod javowy bardzo często mający więcej nulli niż rzeczywistych wartości. Ponieważ artykuł nie jest o xmlach i klasach javy wypełnionych po brzegi annotacjami to zrobimy wygodny skok/uproszczenie i dla potrzeb edukacyjnych "ekstrakcję" zasymilujemy na zwykłej takiej javowej mapie.

Bezpieczny i silnie typowany kod Javy 9

Także oto wspomniana mapka na potrzeby edukacyjne :

Map<String, String> m1 = Map.of("X509SKI", "dupa");

No i teraz najważniejsze. Chcemy wydobyć ten element kiedy tak naprawdę nie wiemy czy on tam jest, robimy to tak :

 Optional<X509Data> result = Optional
                .ofNullable(m1.get("X509SKI")).map(X509Data.X509SKI::new)

No i fajnie ale to nie jedyna opcja w tej opcji - przecież możemy mieć "X509CLR" i teraz własnie wchodzi or

 Optional<X509Data> result = Optional
                .ofNullable(m1.get("X509SKI")).<X509Data>map(X509Data.X509SKI::new)
                .or(
                        () -> Optional.ofNullable(m1.get("X509CLR")).map(X509Data.X509CLR::new)
                )

  • Obserwacja nr1 : Java nie ma czegoś takiego jak "pass by name" w skutek czego trzeba to zasymulować przekazaniem Supplier stąd to "()->" na początku
  • Obserwacja nr2 : zauważcie to .<X509Data>map : jeszcze nie pokazałem jak te typy sa modelowane ale generalnie X509Data to nad-typ i niestety wykrywanie typów w Javie kuleje jeszcze na tyle, że trzeba mu tutaj pomóc i powiedzieć żeby trzymał się typu bazowego.

No i ostatecznie mamy :

Optional<X509Data> result = Optional
                .ofNullable(m1.get("X509SKI")).<X509Data>map(X509Data.X509SKI::new)
                .or(
                        () -> Optional.ofNullable(m1.get("X509CLR")).map(X509Data.X509CLR::new)
                ).or(
                        () -> Optional.ofNullable(m1.get("X509IssuerSerial"))
                        .map(data -> new X509Data.X509IssuerSerial(data, 69))
                );

Closed Algebraic Data Type

Wróćmy teraz do modelu danych. Mamy pewien abstrakcyjny typ z określonymi jego realizacjami X509SKI, X509CosTamInnego itd. Inne języki niż Java wspierają taki typ przy pomocy konstrukcji, która z jednej strony nie jest finalna w nad-klasie a z drugiej pozwala jedynie na zadeklarowanie jasno określonej listy pod typów. (więcej na ten temat : http://pawelwlodarski.blogspot.com/2017/02/closedclosed-principle.html) . W javie okazuję się jest też pewien "sposób" na takie rozwiązanie. Pod spodem zobacyzmy ograniczoną listę klasy wewnętrznych i klasę zewnętrzna z prywatnym konstruktorem -> czyli nic poza ta klasą nie stworzy nowej podklasy bo nie ma dostępu do konstruktora nadklasy - w teorii tak to własnie powinno działać.

//Java sealed type dla ubogich
class X509Data {

    private X509Data() {
    }

    final static class X509SKI extends X509Data {
        final String value;

        X509SKI(String value) {
            this.value = value;
        }
    }

    final static class X509CLR extends X509Data {
        final String value;

        X509CLR(String value) {
            this.value = value;
        }
    }

    final static class X509IssuerSerial extends X509Data {
        final String name;
        final Integer number;

        X509IssuerSerial(String name, Integer number) {
            this.name = name;
            this.number = number;
        }
    }
}

Wygodniej w Kotlinie

Ogólnie fajnie, że java się rozwija i stara się nadganiać ale niektóre rzeczy wciąż wyglądają jak wiadro z gównem. Toteż uwagę mą przykuwa coraz popularniejszy Kotlin, który pod kontem ekspresji jest dla mnie gdzieś pomiędzy Javą a Scalą - i co dobre wydaje się być jeszcze przed granicą gdzie nie udostępnia potężnych ale i niebezpiecznych mechanizmów, którymi dzieci w korporacjach robią sobie krzywdę. A co najważniejsze dla naszego przykładu - ma natywne wsparcie dla rekordów danych oraz typów algebraicznych

sealed class X509Data
data class X509SKI(val value:String):X509Data()
data class X509CLR(val value:String):X509Data()
data class X509IssuerSerial(val name:String,val number:Int):X509Data()

I mając rozwiązanie na największy ból obecnej javy czyli definicje małych klas rozjebane na dwa ekrany możemy przejść do naszego głównego przykładu

val m1=mapOf("X509CLR" to "dupa")

val result:Optional<X509Data> = Optional
            .ofNullable(m1["X509SKI"]).map<X509Data>(::X509SKI)
            .or{ Optional.ofNullable(m1["X509CLR"]).map(::X509CLR)}
            .or{ Optional.ofNullable(m1["X509IssuerSerial"]).map { data -> X509IssuerSerial(data,69) } }

Okazuje się, że Kotlin bardzo dobrze radzi sobie z konwersją pomiędzy swoimi lambdami a Javowymi SAM types. Trzeba tylko przywyknąć do tych klamerek takich i będzie ok.

Edukacyjna rola Scali

Operator Optional.or, który jest nowością w Javie 9 był od dawna dostępny w scali jako Option.orElse. Poniżej jego definicja z charakterystyczną strzałką "=>" przed argumentem. Ten zapis oznacza "call by name" czyli argument jest zwyczajnie ewaluowany leniwie. Ponieważ w Javie nie ma czegoś takiego to zwyczajnie trzeba było się tam spodziewać Supplier. No i tak znając jeden język można z marszu przejść do używania "nowinek" w innym języku.
  @inline final def orElse[B >: A](alternative: => Option[B]): Option[B] =
    if (isEmpty) alternative else this

Podsumowanie

Dzisiaj połączyliśmy technologię w miarę nowoczesną - Java 9 - która mimo wszystko trochę jest w plecy za resztą świata - z przykładem technologii tak starej, że pewnie wasz dziadek używał kiedy w dyliżansie przemierzał dziki zachód. To co chciałbym byście wynieśli to jednak ilustracja zastosowania operatora na poziomie typów Optional. Bo to już nie takie pseudo "ifowanie" w stylu

Zaczytałem se jakiegoś syfa
użyje ifPresent czy getOrElse
i z Optionala mam kurwa zwykłego IFa
To co pojawia się w przypadku Optional.or to - algebra - tak samo jak algebra boolea dla true|false . Java zmierza w dobrym kierunku (byleby jakiemu geniuszowi nie przyszło do głowy jebnąć na Optional jakiejś annotacji @orrable która w runtime obrzyga całe typesafety, nie , prosze nie, nie , poprostu nie)

poniedziałek, 25 września 2017

Patronat medialny nad Konferencją ByteMyCode

Na początek zgodnie z poradami książki* o marketingu internetowym, która kupiłem w 2009 roku w ksiągarni** elektronika - i w tej książce było napisane by od razu walnąć główny link odpowiednim akcentem

*wytłumaczenie dla młodzieży - książka to taki rodzaj tabletu, który nie pobiera prądu ale nie ma możliwości customizowania wyświetlanej treści
** księgarnia - taki sklep firmowy z książkami jak analogiczny iSpot ale różni się tym, że pracownicy mogą chodzić we własnych ubraniach

=============> !!!!!Konferencja Byte My Code Kliknij Tutaj!!!!! <===========

Występują Java Czempioni:

Oraz:

I wiele innych atrakcji!!

=============> !!!!!Konferencja Byte My Code Kliknij Tutaj!!!!! <===========

Ok skoro część marketingową mamy za sobą. To krótkie wyjaśnienie o co chodzi. Otóż okazało się, że tego bloga czytają nie tylko ruskie boty ale także gdzieś jakaś żywa tkanka jest tą treścią zainteresowana. No i zgłosiła się miła pani czy bym nie objął nad konferencją patronatu --> to obejmuję i jeszcze raz link https://bytemycode.pl/"

Jest to o tyle miłe, że w zasadzie ten blog to dla mnie generator wielu problemów. Dlaczego? Nie znajdziesz tutaj zbyt wielu tutoriali a raczej próbę zmuszenia ludzi do refleksji. Aby zmusić ludzi do refleksji trzeba zaburzyć ich porządek świata. Aby zaburzyć ich porządek świata trzeba zaburzyć ich widzenie siebie. A to w skrócie oznacza, że np. by wymusić na kimś z wieloletnim doświadczeniem w javie proces samo-refleksji trzeba mu wytłumaczyć, ze fakt iż zna zestaw annotacji z hibernate nie robi z niego programisty. No i teraz pytanie retoryczne czy owy proces sprzyja lajkom czy hejtom? Dlatego też jest to naprawdę miłe gdy ktoś jednak docenia ten wysiłek, chce czytać tę treść i zależy mu by tutaj coś się pojawiło.

Są dwie wejściówki do rozlosowania. Aby wziąć udział w losowaniu w komentarzach poniżej napiszcie proszę tak w kilku zdaniach dlaczego lubicie czytać tego bloga. Najlepsze dwie wypowiedzi otrzymają wejściówki. ..
..
..
oczywiście kurwa żartuje, będzie losowanie standardowo na meetupie ---> LOSOWANIE

WPIS NA FACEBOOKA PL:

Na koniec aby pokazać moją niezależność jako autora tego bloga wkleję tutaj notatkę przeznaczoną na fejsa

Spotkanie z #JavaChampions: Wrocław, 7 października! UBS Polska zaprasza na pierwszą edycję konferencji Byte My Code, podczas której spotkacie #JavaChampions: Josha Long oraz Kirka Pepperdine, jak również wyśmienitych specjalistów #Java: Łukasza Szydło, Sebastiana Malaca, Michała Kordasa i innych. Wszystkich prelegentów oraz szczegółowe informacje znajdziecie na: https://bytemycode.pl Nie czekajcie z rezerwacją biletu - na spotkanie z #JavaChampions przewidziano podczas pierwszej edycji tylko 250 biletów! First come, first served!

I jeszcze zdjęcie gór.

czwartek, 29 czerwca 2017

Na granicy systemu

Nie wiadomo czy to miejska legenda czy przypadek prawdziwy ale podobno w Indiach mają taką sztuczkę na łapanie małp : wsadzają banan w butelkę a butelkę zakopują. Teraz małpka wyciąga łapkę, łapie banan zaciskając paluszki i już łapka tam zostaje. Małpka banana nie puści i będzie tak czekała zaklinowana aż kłusownicy po nią przyjdą.

Przykład jest tak absurdalny, że doczekał się znaczenia metaforycznego kiedy to ludzie chwytają tak swoje myśli, które to wiążą ich w mentalnych klatkach. I ludzie ci tak będą czekać w tej klatce a myśli nie puszczą. Jaki to ma związek z dzisiejszym artykułem. W zasadzie żaden ale to fajna anegdota a zawsze te pierwsze akapity są najtrudniejsze.

Granice Systemu

Dzisiejszy artykuł sponsorują dwie funkcje :

  • toHex : String => Hex
  • createUser : Json => User
Obie w założeniu działają na granicy systemu czyli zupełnie niekontrolowany 'input' zamieniają w typ domenowy. Coś jak na rysunku

Hex i User... z pozoru mają ze sobą niewiele wspólnego - tak jak i obydwie funkcje toHex oraz createUser. Ale jest pewna wspólna rzecz, jedna niespodziewana i siejąca spustoszenie w systemach IT rzecz. Nawet bez zaglądania do ich definicji można z dużym prawdopodobieństwem rzecz - obydwie te funkcje kłamią!

Program jako funkcja całkowita

Ten akapit będzie streszczeniem innego artykułu : Program jako funkcja całkowita i generalnie chodzi o to, że bug w systemie jest wtedy jak myślimy że nasz program-funkcja jest zdefiniowany dla konkretnych danych wyjściowych a nie jest :( . Na przykład nasz program to aplikacja webowa przetwarzająca request w response czyli Request=>Response no i dla requestu z sqlinjection zachowanie poprawne nie było zdefiniowane - czyli na przykład wyświetlenie błędu - i całość zakończyła się katastrofą.

No i teraz jak mamy "miniprogram" Json => User to o ile dla każdego błędnego jsona nie zwracamy użytkownika Roman to to nie może działać!!!

Null object (czy coś takiego)

Kiedyś pamiętam był na tapecie taki koncept by nie zwracać z funkcji nuli tylko na przykład puste listy ,puste tablice, puste mapy, puste... puste k**wa cokolwiek. Potem pamiętam serię artykułów, że ludzie trochę zaczęli przekombinowywać i zwracać "puste" obiekty nie tylko gdy brakowało danych w bazie czy coś w ten deseń ale gdy np. wystąpił błąd. Jest to zamiatanie problemu pod dywan. Bardzo złe. Bardzo niedobre. Co możemy zwrócić jak jest zły JSON? No jeśli to nie jest na chama robiony CRUD to latający obiekt z "wrong variance" czy jak to się mówi po angielsku - spowoduje tylko kłopoty

Podobnie dla HEX -> "FFAA00" to dobry hex , "DUPA" to zły hex (acz trywialna implementacja String.getBytes obydwa zamieni w poprawny typ z poprawnymi pojedynczymi wartościami ale złym znaczeniem!!!) . I teraz weźmy dwie szkoły. Po pierwsze możemy rozszerzyć rezultat funkcji String => Option[Hex] czy JSON => Option[User]

Druga szkoła to dobrze znane nam wyjątki. Tutaj sztuczka polega na tym, że blok try-catch niejako ogranicza dziedzine funkcji ale w trochę nieintuicyjny sposób bo albo działamy na poprawnym zbiorze wartości i dostaniemy poprawny rezultat albo działamy na niepoprawnym zbiorze i ... zakrzywiamy czasoprzestrzeń lądując "kiedyś" i "gdzieś".

Problem z null

Na nulla możemy także spojrzeć z perspektywy funkcji częściowej i całkowitej. Otóż jeśli np. mam funkcje String => Option[Hex] i jest ona całkowita ze względu na każdy element zbioru String to można szybko zamienić ją na częściową wprowadzając null gdyż wtedy domena zamienia się na "każdy element zbioru string i do tego null" czyli aby znowu uzyskać funkcję całkowita potrzebujemy dodać dedykowaną obsługę nulla.

Czas ma znaczenie

Znak zapytania na ostatnim diagramie to bardzo często jakiś tam ErrorHandlingController. To jedno ale druga sprawa to, że nie za bardzo mamy czas pomyśleć bo to dzieje się teraz!! . Skok w try następuje od razu. W przypadku szkoły pierwszej wspomnianej wcześniej mamy pewien typ. Ten typ mamy tu i teraz i możemy się chwile zastanowić - on nie ucieknie. Innymi słowami try-catch to wykonanie instrukcji a Option[User] czy Option[Hex] to obliczenia w toku. Co więcej obliczenia, które nawet nie muszą być jeszcze wykonane jeśli mamy leniwy język!

No i mając ten Option mogę stworzyć jego dalsze przetwarzania przy pomocy dostępnych kompozycji jak option.orElse(otherOption) . Przy skokach w try oczywiście też mogę dać obsługę wyjątków ale zazwyczaj nie wynika taka operacja z typów co dla RuntimeException zwiększa ryzyko, że o tym zapomnimy faktycznie o obsłudze. No i oczywiście sam skok to nie jest jakaś zwracana wartość tylko no... "skok" także nie można tego za bardzo komponować co może zakończyć się tuzinem zagnieżdżonych klamerek przy bardziej wyszukanej obsłudze.

Szkoła dwa_i_pół

W GO popularne jest inne podejście. Często funkcja zwraca potencjalny rezultat i potencjalny błąd przez co jest całkowita.

func aleBezpieczneDzielenie(a, b float64) (float64, error) {
 if b == 0.0 {
  return 0.0,errors.New("oj neidobrze")
 } else {
  return a/b , nil
 }
} 
Problem z tym przykładem jest taki, że pomimo, iż potencjalne - to jednocześnie wartość i błąd musza być konkretne stad mimo wszystko zwracane jest 0.0 co jest dziwne - no i w sumie to ode mnie zależy czy błąd sprawdzę czy nie także nadal może się popsuć. Oczywiście nie znam tak dobrze jeszcze tego języka i być może jakaś opcja jeszcze gdzieś tam inna istnieje by to inaczej obsłużyć.

Szkoła trzecia - siła typów

Zaczęliśmy od A=>B by dojść do teoretycznie bezpieczniejszego A=>Option[B] czyli bezpiecznie przyjąć dane z zewnątrz systemu

  • intoSystem: String => Option[Hex]
  • intosystem: JSON => Option[User]

Ale co gdy mimo wszystko chcemy zachować funkcję String => Hex ? Cóż wtedy, wtedy cóż? Być może to jakaś funkcja biblioteczna a może po prostu nam pasuje tak jak jest? Zawsze można to zrobić to podejściem ze szkoły pierwszej czyli coś w stylu lift : (A=>B) => (A=>Option[B]) ale można też rozwiazac problem od drugiej strony ograniczając dziedzinę do tylko prawilnych User-JSONów lub Hex-Stringów!

Lub na jeszcze innym rysunku zobaczymy porównanie rozwiązania błędnego z funkcją częściową, funkcję całkowitą poprzez rozbudowanie domeny wyniku, oraz funkcję całkowitą poprzez ograniczenie domeny wejścia do bezpiecznego typu

W kodzie by to wyglądało mniej więcej tak:

val validate: String => Option[SafeHexString] = ...

val toHex : SafeHexString => Hex

validate(input).map(toHex)

Podsumowanie

Artykuł zaczęliśmy od śmiesznej anegdoty o małpce złapanej w butelkę (po która przyjdą kłusownicy - czyli może nie tak śmiesznej). Miało to nie być związane z samym artykułem ale jednak będzie. Metafora banana i trzymania się kurczowo nawyków. Gdy Optional pojawił się w Javie wielu programistów, z którymi miałem styczność traktował go jako takiego "wrapper na nulla". "Wrapper na nulla" należy do domeny Javy, być może dobrze czasem wznieść się na bardziej abstrakcyjny poziom by zobaczyć inną istotę używanych konstrukcji. Jeśli komuś nie pasuje metafora z małpką która będzie zjedzona w Indiach to inna opowieść to "Brzydkie Kaczątko" ale trzeba by ją trochę nagiąć by kaczątko stało się monadą niewykorzystanego potencjału.

niedziela, 4 czerwca 2017

Rozszczepienie obiektu

Jest takie pojęcie - "myślenie lateralne" - stworzone przez E. De Bono, który jest takim specem od ogólnej nauki i przyswajania wiedzy. W tejże książce znajdują się (takie fajne) rysunkowe przykłady graficznego tworzenia jakiegoś rozwiązania gdy dysponujemy ściśle określonym kształtem figur. Zabity na pałę kształt figury symbolizuje sztywne przekonanie a problemy zbudowania rozwiązania symbolizują problemy szablonowego podejścia do rozwiązania problemu gdy mamy tylko sztywne przekonania.

Na kolejnych stronach sztywne kształty są rozbijane na nowe i w końcu budujemy rozwiązanie z nowych przekonań. Jak to ma się do programowwania?

Jest takie powiedzenie "jak programujesz młotkiem to wszystko wygląda jak gwoździe" . Osobiście na tym blogu w ostatnich latach staram się to stwierdzenie odnosić do Javy - z prostego powodu. Sam kiedyś myślałem, że "programowanie" zamyka się w segregacji interfejsów by było bardziej "domenowo", dorzuceniu stosu annotacji by framework przyjął nasz kod i podregulowaniu JVM by było szybciej.

Dlatego też polecam by każdy podszedł do nauki kolejnego języka (nawet bez szybkiej perspektywy zastosowania go w praktyce), który zmusi go do wypróbowania zupełnie nowych podejść i sprawi, że sztywne kształty zostaną rozbite i nowa kompozycja rozwiązania stanie się realna.

Rozszczepienie obiektu

Mamy klasy, które mają pola - pola ustawiamy seterami lub konstruktorem a pobieramy getterami. Inne nazewnictwo tego mechanizmu to "mutatory" i "akcesory" (czy "accessory"). getter są połączone z daną instancją obiektu ... ale czy muszą?

Obiekty w javie to (chyba) częściej będą jakieś bezstanowe kontrolery/repozytoria lub zwykłe DTO/rekordy danych (pole+getter) aniżeli jakaś wyrafinowana enkapsulacja zmiennego/mutowalnego stanu. Wynikać to poniekąd może z faktu, że dane i tak trzeba zapisać w bazie toteż "życie" takiego obiektu jest bardzo krótkie. Jeden z niewielu przykładów gdzie z mojego doświadczenia takie prawo nie obowiązywało to akka-persistance gdzie aktor może kumulować prywatny stan mając zabezpieczenie w postaci "event journala" - nie wykluczone, że podobna sytuacja występuje w domenach i narzędziach, z którymi przyjemności nie miałem pracować ale chyba w CRUDach produkowanych w Łódzkich szwalniach raczej przeważa model "wyciąg z bazy, zmien i wciśnij na powrót".

I taka "obiektowa" reprezentacja rekordu danych np "User" nam do dalszych rozważań spokojnie wystarczy.

No bo teraz zobaczmy taką konstrukcję.

class Street(name:String)
class Address(s:Street,city:String)
class User(name:String, address:Address)
Mając cały czas ten pierwotny sztywny kształt "getter w instancji" próbując dostać się do nazwy ulicy otrzymamy .
street.getName
address.getStreet.getName
user.getAddress.getStreet.getName

Wiele osób sie skręci na ten widok bo tyle kropek w jednej linii uważane jest (poniekąd prawidłowo) za antywzorzec i nazwane "train wreck". Oburzenie jednocześnie byłoby i nie byłoby uzasadnione. Otóż spierdolina niewątpliwie może się pojawić gdy np. w obiekcie mamy listę produktów i staramy się pobrać najdroższy z nich

Można to zrobić źle

class Klasa{
private List<Product> products = ...
getProducts... 
}

klasa.getProducts.sort(desc).takeFirst

lub trochę lepiej :

class Klasa{
private List<Product> products = ...
findMostExpensive = products.sort(desc).takeFirst 
}
klasa.findMostExpensive

I to dla mnie wydawało się jasne i oczywiste gdy byłem w epoce javy ale nic nie stoi na przeszkodzie by cały czas mieć enkapsulację danych a wynieść metodę do innego obiektu, który może działa w zakresie prywatnym danej instancji. Brzmi trochę dziwnie ale np. scala umożliwia taką konstrukcje przy pomocy companion object

class Klasa{
private List<Product> products = ... 
}

object Klasa{
def findMostExpensive(instance:Klasa) = instance.products.sort(desc).takeFirst 
}


//in a different package
import Klasa._
val k=new Klasa
findMostExpensive(k)
i to może zadziałać tylko jeśli na liście będą metody, które sobie wymyśliłem. A jeśli mamy jakieś "superSpecjalistyczneBiznesoweSortowanie" ? Wtedy można wywołać ją "explicit"
superSpecjalistyczneBiznesoweSortowanie(instance.products).takeFirst
Ale jeśli spojrzeć na superSpecjalistyczneBiznesoweSortowanie nie jak na metodę a jak na funkcję "List[Products] => List[Products]" wtedy może mieć miejsce zgrabna kompozycja
val findMostExpensive : Klasa => Product = 
getProducts andThen superSpecjalistyczneBiznesoweSortowanie andThen getFirst 

Czym jest getProducts? To getter w postaci funkcji, która przyjmuje daną instancje. Normalnie jak mamy "getCos" w klasie to tak "na pierwszy rzut oka" nie ma parametrów ale na rzut oka drugi w bajtkodzie będzie przekazany parametr "this" czyli mamy taki "coupling" gettera do danej konkretnej instancji. Możemy zrobić "decoupling" oraz odpowiednio sterować zakresem dostępu by getter wyciągał bebech w określonych miejscach.

ok, rozbijając pierwotny kształt dochodzimy do trochę innego sposobu budowania rozwiązania -> podążajmy dalej tym tropem

Optyka

Jeśli już mamy getName, getStreet i getAddress wtedy bez problemu możemy całość skomponować w jednego gettera

val getStreetName : User => String =  getAddress andThen getStreet andThen getName
no fajnie ale co nam to daje? Jest to klasyczny przyklad user, ktory ma adres. Firma może mieć adres i zakup moze miec adres. no i można getter "reużyć" w kompozycji.
val getStreet : Address => String = getStreet andThen getName
val companyStreet: Company => String = companyAddress andThen getStreet
val purchaseStreet: Purchase => String = purchaseAddress andThen getStreet
No i jest rejuse poprzez ładną kompozycję. A to dopiero gettery. Magia zaczyna się gdy poskładamy tak sobie settery i może skromna wizualizacja poniżej.

Wspomniany mechanizm nie został odkryty tu i teraz. To jest popularne podejście FP zwane Lens i realizowany przez biblioteki Monocle(https://github.com/julien-truffaut/Monocle) lub rodzime Quicklens(https://github.com/adamw/quicklens). A monocle dlatego, że lens jest częścią ogólniejszego mechanizmu zwanego optics i jest tam kilka innych ciekawych rzeczy.

Iso

Co jeśli w jednym kontekście numer ulicy to String ale gdzie indziej operujemy "bardziej domenowym" StreetNumber. Można łatwo dokomponować do gettera/settera konwersję "w tę i na zad".

Optional

Nulle to chuje. Kosztują wiele błędów, czasu i pieniędzy. Dlatego brak wartości należy modelować przy pomocy Option/Optional/Maybe . No i możemy Optional wkomponować w nasze gettery tak, że dalsze soczewki będa komponowane w kontekście potencjalnego braku wartości i na koniec dostajemy np. Option[StreetNumber]

List

Jeśli Option to zero lub jeden element - tak lista to zero lub n elementów. No i teraz taka opcja. Powiedzmy, że mamy jebany javowy moloch wygenerowany z SOAP.

getUser composeTraversal getPurchases.map(_.price) composeFold reduce  // to pseudokod
I to może nam dać szybki raport o sumie zakupów

Functor

No to jak jest Option i jest List to pewnie będzie i generalizacja w postaci funktora. I np. w monocle mamy:

  /** modify polymorphically the target of a [[PLens]] using Functor function */
  def modifyF[F[_]: Functor](f: A => F[B])(s: S): F[T]

Pryzmat

Ta koncepcja raczej będzie opcja w świecie JAvy. Pryzmat jest w stanie rozszczepić abstrakcyjny typ ADT do konkretnego podtypu. Klasycznym przykladem jest

sealed trait Json
case class JStr(s:String) extends Json
case class JNum(i:Int) extends Json

//i to tutaj skopiowane prost z dokumentacji
val jStr = Prism[Json, String]{
  case JStr(v) => Some(v)
  case _       => None
}(JStr)

Warsztaty

Temat poruszony w tym artykule został zainspirowany książką "[TYTUL]" która stanowi kręgosłup nowego cyklu warsztatów na łódzkim JUGu (JUG - Just User Group) "Modelowanie Domeny z FP". Pierwsze spotkanie już się odbyło i kilkanaście osób na tak wyspecjalizowany temat interpretuję jako zainteresowanie duże.

Na

pierwszym spotkaniu uczyliśmy się jak wykorzystać monady do modelowania efektów (tak padło słowo monada i nie rozumiem tego podniecenia tu i tam - "nie wypowiem słowa na M"- po to kurwa ma ta konstrukcja nazwę by jej używać) oraz jak komponować operacje biznesowe będące czystymi funkcjami z efektami. Kolejna część po wakacjach bo jak jest lato i 30 stopni to nikomu się nie chce przychodzić i jest dla mnie strasznie frustrujące jak zapisuje się 30 osób a przychodzi 5. Także wrzesień - manipulacja złożonymi typami przy pomocy "optyki" a później pewnie użycie Monad Reader jako repozytoriów.

No a artykuł był o tym by zburzyć dogmaty na których zbudowane są nasze przekonania - w tym przypadku przekonanie, że zawsze getter musi być związany z dana instancją - a jak już to się stanie to możemy budować rozwiązania z nowych "kształtów"

Post SKRYPTum

I tutaj jeszcze link z mojej próby podejścia do tego tematu dwa lata temu : http://pawelwlodarski.blogspot.com/2015/08/soczewkiobiektywylenses.html

niedziela, 7 maja 2017

Jak w Javie można podkradać fajki czyli private nie takie prywatne

We wstępie do takiej jednej książki autorstwa Roberta Cialdiniego o obronie przed manipulacją jest przykład pewnego Indora (nie chodzi o popularne na siłowniach "Indor Cycling" ale o zwykłego żywego ptoka) i tamże naukowcy podrzucili temu prawdziwemu Indorowi sztucznego Indora, a w zasadzie nie indora tylko coś co wydawało indorowate dziwięki i to wystarczyło aby prawdziwy indor zaczął traktować odtwarzacz kasetowy jako swoje własne indorze piskle.

Innymi popularnymi przykładami bezrefleksyjnego zachowania bodziec-reakcja w świecie przyrody będzie oczywiście czerwona płachta na byka, wydzielanie śliny po usłyszeniu dzwonka przez psa Pawłowa oraz oburzenie niektórych programistów Java gdy przed polami w klasie nie pojawia się słowo private.

Pola (Niepotrzebnie)Prywatne

Jak kończy się bardzo często w praktyce dodawanie modyfikatora private do pól? Gdy chcemy stworzyć po prostu reprezentacje danych a język, w którym programujemy ma tylko klasy - wtedy tworzymy klasę, która ma być po prostu danymi - dodajemy pola i generujemy getter/settery.

Ewentualnie jeśli jesteśmy w tej branży odpowiednio długo to jednak nie dodajemy setterów. Można poczytać choćby w już 10 letniej Effective Java o zaletach (z angielska) "klas niemutowalnych" i stworzyć konstruktor, który będzie dbał o to by w naszym obiekcie nie pojawiły się śmieci w stylu sto-dwudziesty-ósmy dzień tygodnia, dajemy final przed polami i jest elegancko. A jak już jest obiekt i uda nam się uniknąć jałowej dyskusji czy to ma być rich/fat/poor/flat/anemic bo potrzebujesz rekord danych - a twój język zarówno technologicznie jak i ideologicznie nie dopuszcza możliwości stworzenia czegoś takiego - wtedy trzeba na końcu nazwy dodać "DTO" i następąpi kolejny cykl bodziec-reakcja gdzie na niektórych ludzi zadziała to jak mellissa i przestaną ci podsyłać posty Martina Fowlera z przed 15 lat.

Jak już mamy poprawnie stworzony obiekt (pamiętaj pamiętaj pamiętaj : "Data Transfer Object" a nie żaden "Record" - Wszystko jest obiektem a Record to takie nieobiektowe) wtedy trzeba dać możliwość dostępu do danych. No i jest jakaś teoria za geterami, która nazywa je (z angielska) "Akcesorami" i daje jakieś tam uzasadnienie że coś tam można prywatnie zmieniać w implementacji klasy bez zmiany jej interfejsu. O i tutaj zaraz na prawo jest taki post gdzie na obrazku w nagłówku jakaś ładna pani czyta wsadowy plik DOSa -> Accessors and Mutators. No i jak to w tego typu postach mamy trochę z dupy naciąganą sytuacje gdzie w Rekordzie obiekcie Person trzeba było zmienić sposób w jaki przechowuje się middle name ze Stringa na Tablice. No niezwykle praktyczny przykład.

Oczywiście może pojawić się krytyka, że przecież muszą być gettery bo pola sa prywatne a przecież musza być prywatne bo jak nie będą to każdy tam wepchnie byle syf. Nie nie muszą. Mamy coś takiego jak juz wspomniane final i jak pole jest final to ustawiasz sobie raz to pole public final String name w konstruktorze. Na pewno się to nie uda gdy utrzymujemy projekt z przed 150 lat gdzie standard JSP wręcz wymusza by dostęp do danych był przez "getCosTam". To się nazywa konwencja i taką sobie przyjęli - być może inne Zręby frameworki też wymuszają taką konwencję. Cóż jak chcesz ich używać to nie masz wyjścia ale takie uzasadnienie "bo framework wymaga" ma niewiele wspólnego z perspektywą projektowania/modelowania systemu.

Apropo frameworków to możemy czasem wpaść na pomysł, że w sumie chcemy gdzieś zapisywać dane a później np. chcemy wyświetlić listę "Personów" na jednej ze stron naszego CRUDa. No i jest ten fajny framework, który mi wyciąga dane z bazy i pakuje do klas i w ogóle. I wystarczy tylko dać annotacje tu i tam. A no i trzeba dodać bezparametrowy konstruktor -> no bo frejmłork stworzy sobie pustą klasę i bedzie tam wpychał dane jak mu wygodnie, być może przez refleksję będzie je wpychał, być może z totalnym wyjebaniem na takie słówka jak private. No i wtedy nie ma wyjścia - jak masz konstruktor bezparametrowy to już pola nie mogą być final i musza być private. Chyba nie ma wyjścia --> z tej sytuacji bo taki standard jest. No cóż.

¯\_(ツ)_/¯

Pola (potrzebnie)Prywatne

Kiedy tworzenie pola prywatnego ma sens? Zawsze kiedy masz stan, który musisz zmieniać. To czy musisz mieć ten stan i czy to dobrze czy nie dobrze, że go zmieniasz to już inna historia. Obiekt może przechowywać stan, który na skutek interakcji z resztą systemu może ulegać zmianie i zależy ci aby przede wszystkim uniemożliwić te zmiany z zewnątrz. Z racji wydajnościowych lub innych ważnych powodów możesz chcieć operować efektywnymi acz umożliwiającymi machloje strukturami danych i a na zewnątrz chcesz operować bezpiecznymi niezmiennymi strukturami. I tutaj na przykład obiekt może manipulować tablicą bajtów czy StringBuilderem a z zewnątrz będzie można dane tylko jak string odczytywać. Plus jak sobie włączymy 2,3,4 wątek to bardzo bardzo nam powinno zależeć aby stan zmieniać tylko z jednego zabezpieczonego miejsca.

No i dla przypomnienia raz jeszcze. Jak sobie napiszesz klasę Person i tam jest getName, które zwraca prywatne pole typu String to nic nie ukrywasz - taka sztuka dla sztuki ten geter, a jak jeszcze masz seter bo jakiś framework tego chce to ani nie ma żadnego ukrywania ani żadnej kontroli. Cześtym argumentem pojawiającym się tutaj jest - "bo jak mam settera to walidację sobie mogę zrobić" - spójrz w lustro , spójrz sobie w oczy w tym lustrze i odpowiedz sam przed sobą ile razy twoja interakcja z setterem to było coś więcej niż IDE->GENERUJ.

W każdy razie po tym wstępie (tak to był wstęp, długi ale wstęp) przejdziemy do zasadniczej części artykułu. Jeśli już masz taki uzasadniony prywatny stan - to niespodzianka - on wcale taki prywatny w tej Javie nie jest.

Pola (Nawet nie wiesz, że nie)Prywatne

Obiektem - a w zasadzie dwoma obiektami - naszych badań będą dwie instancje klasy Osoba. Klasa ta prezentuje koncept biednego człowieka relaksującego się przy pomocy używek.

class Osoba{
    private int fajki;
    public final String name;

    public Osoba(int fajki, String name) {
        this.fajki = fajki;
        this.name = name;
    }

    public String relax(){
            if(fajki==0)
                return name + ": o kurde gdzie moje fajki!";
            else{
                fajki=fajki-1;
                return name + ": zostało mi "+fajki+" fajek";
            }
    }

    void podpierdolFajki(Osoba ktosInny){
        this.fajki=ktosInny.fajki;
        ktosInny.fajki=0;
    }
}
Palenie szkodzi zdrowiu. Poniższy przykład służy tylko edukacji bo prościej sobie wyobrazić, że ktoś podprowadzi fajki a nie witaminę B6.

I co my tam mamy? Jest publiczne pole name czyli już będzie (David) lincz na forach javowywch. Ale jest też prywatne pole fajki i nie ma metody getFajki. Nie powinno nikogo interesować jak ktoś chce sobie puścić dymka. No i w zależności od stanu prywatnego fajek w kieszeni tenże relaks się uda albo nie uda.

Mamy także i jeszcze jedną niepokojąca metodę. Metoda ta ma zakres pakietowy (o czym wielu zapomina, że taki jest bo jest niewidzialny) czyli możemy założyć, że to metoda prywatna dla danej paczki co jest także dosyć wygodne by podzielić logike na kilka klas w pakiecie bez pokazywania ich na świat.

No i w tej metodzie tak trochę wygląda jakby jedna instancja mogła zmienić stan w innej. czy to się kompiluje? Niestety albo stety tak

Osoba stefan=new Osoba(0, "Stefan");
Osoba babkaStefana=new Osoba(10, "Babka Stefana");

stefan.podpierdolFajki(babkaStefana);

System.out.println(stefan.relax());
System.out.println(babkaStefana.relax());
I chociaż Stefan nie ma fajek to jednak w wyniku szfindlu i kompilacji.
Stefan: zostało mi 9 fajek
Babka Stefana: o kurde gdzie moje fajki!
Dlaczego tak się stało? Otóż private w Javie nie jest dla obiektów ale dla klas! Dlatego też dwie instancje tego samego obiektu mogą sobie te prywatne pola czytać. Może to się przydać przy tzw "konstruktorach kopiujących" kiedy tworzymy nowy obiekt na podstawie starego. No i oczywiście coś takiego nie przeszkadza kiedy mamy udawane-obiekto-dane gdzie można sobie ustawić cokolwiek skądkolwiek przy pomocy settera.

Kiedy takie podkradanie danych przez instancje tej samej klasy zaczyna być zauważalne? Kiedy w deklaracji klasy pojawia się generyk oraz zależność pomiędzy klasą a typem generyka jest Covariant (więcej może tu : kowariantne-konsekwencje.html")

Pola (Naprawdę)Prywatne

Do eksperymentu wystarczy nam taka deklaracja :
class Opakowanie[+A](private var mutable:ListBuffer[A])
No i co sie teraz stanie gdy będziemy tworzyć instancję?
val o=new Opakowanie[String](ListBuffer[String]())
Błąd !
Error:(14, 34) covariant type A occurs in invariant position in type => scala.collection.mutable.ListBuffer[A] of variable mutable
class Opakowanie[+A](private var mutable:ListBuffer[A])

o so chozi? Otóż z racji, że Opakowanie[String] jest podtypem Opakowania[Object] możliwe jest szachrajstwo gdzie Opakowanie[Int] podszywając się za Opakowanie[Object] podrzuci Inta do Listy Stringów - a tego nie chcemy. Nie chcemy usuwac tego +A bo chcemy miec relację Covariant - co zrobić? Otóż rozwiązaniem jest prywatny zakres instancji

class Opakowanie[+A](private[this] var mutable:ListBuffer[A])
Jest coś takiego w Scali - Zakres prywatny Instancji .No i teraz żodyn tu int anie podrzuci Żodyn!!!

Podsumowanie

O czym dokładnie był ten artykuł? Na poziomie mechanicznym o tym, że niektóre mechanizmy języków nie zawsze działają tak jak nam się wydaje. Na poziomie ideologicznym był to apel by jednak do pewnych rzeczy nie podchodzić jak maszynka bo przemysł nas zaprogramował by lubić annotacje i "Beany". Czasem warto zajrzeć jak coś działa w innych językach a już na pewno uważać z używaniem cytatów Fowlera czy Unkla Boba jako argumentów - każdy przez to przechodzi i ja też przechodziłem - to, że konsultant, który oferuje swoje usługi płatnie wydał kilkanaście lat temu opinie jeszcze nie jest argumentem