
Tokarka przy pracy widziana "oczami" AI
Dawno, dawno temu… Za górami za lasami… W małym bieszczadzkim miasteczku pewien młody, pełen zapału chłopak odebrał swój upragniony dyplom inżyniera. Od teraz mógł projektować własne śrubki w AutoCAD’zie i samemu programować tokarki. Młodzieniec jeszcze wtedy nie wiedział, że to wszystko do niczego mu się w życiu nie przyda.
…aż do teraz.
Aby nadać kontekst dzisiejszemu wpisowi zacznijmy od małego wyjaśnienia przestoju na blogu. W chwili kiedy to piszę jestem w trakcie poznawania tajników frameworka Spring. Czytam książki, oglądam filmy, robię kurs i piszę własne przykłady. Jako, że mamy tu do czynienia z wiedzą całkiem zaawansowaną, to programiści od których ją pozyskuję do żółtodziobów nie należą; tworzą kod wysokiej jakości, bogaty w przeróżne skrótowce i biblioteki nieznane początkującym. Bardzo często korzystają z tzw. „programowania funkcyjnego” aby kosztem zwiększenia abstrakcyjności zyskać na zwięzłości kodu (czyli: „łatwiej się kod czyta, ale trudniej zrozumieć jak działa”).
Ale czym tak właściwie jest to „programowanie funkcyjne”?
FP (dalej będę posługiwał się tym skrótem odnoszącym się do angielskiej nazwy: Functional Programming) jest tak jakby przeciwieństwem OOP (Object Oriented Programming – Programowanie Zorientowane Obiektowo). Pisząc krótko:
OOP koncentruje uwagę na obiektach, a FP na funkcjach – czyli to co z tymi obiektami robimy.
Aby w pełni zrozumieć różnice obu podejść, będą potrzebne solidne przykłady, ale nie byłbym sobą gdybym nie zaczął od suchara dla rozluźnienia napięcia:
Do biura patentowego zgłasza się wynalazca. Przyniósł ze sobą pudełko z owalnym otworem. Kiedy urzędnik zapytał się co to jest, ten z uśmiechem odpowiada, że to nowoczesna maszyna do golenia. Najpierw przykłada się twarz do otworu, następnie wciska przycisk na obudowie, a system brzytew szybko pozbędzie się niechcianego zarostu.
– Ach, tylko do pierwszego golenia!
Usłyszawszy te wyjaśnienia, urzędnik jest zaintrygowany, ale docieka szczegółów:
– No dobrze, ale jak to ma działać skoro każdy ma inne rysy twarzy?
Na co projektant wynalazku odpowiada, szczerze i z uśmiechem:
W tej małej anegdotce mamy już pierwsze wskazówki jak wyglądają różnice pomiędzy OOP i FP. Programowanie obiektowe to profesjonalny golibroda/barber który inaczej podejdzie do każdego klienta, tymczasem programowanie funkcyjne to ta magiczna maszynka, która każdego opitoli na jedno kopyto.
Ale, ale! Ktoś może pomyśleć – acha, czyli OOP jest dobre, bo precyzyjne, a FP złe bo zautomatyzowane. No nie do końca.
Wyobraźmy sobie taśmę produkcyjną której zadaniem jest produkcja kostek do gry. Stoi przy niej 6 pracowników, każdy z własną wypalarką: do strony z jednym oczkiem, z dwoma i tak dalej aż do sześciu. Każda osoba ma jedno i tylko jedno zadanie – podniesienie kostki, przyłożenie wypalarki do odpowiedniej strony i odłożenie kostki z powrotem na taśmę, tak aby kolejna osoba mogła ozdobić następną ściankę.
I tak to idzie przez cały dzień, a ludzie jak to ludzie mają swoje charaktery i potrzeby. Gość od ścianki z trzema oczkami poszedł „na dwójkę” i produkcja stanęła na kilkanaście minut. Potem pracownik odpowiedzialny za „cztery oczka” doszedł do wniosku, że on swoją robotę robi bardzo szybko i w sumie to może od razu robić po dwie kostki na raz, z czego tę drugą bierze zanim jeszcze dostała jakiekolwiek wcześniejsze oczka, a potem jego poprzednicy nie wiedzą z której strony zacząć i zaczyna się kłótnia.

Tymczasem na sąsiedniej, eksperymentalnej linii wszystko robią maszyny. Z boku taśmociągu wystaje ramię z podłączoną wypalarką. Nachyla się na ustawioną wcześniej wysokość kostki i gdy ta przesuwając się na linii najeżdża na chwytak, zostaje automatycznie umieszczona w kleszczach, dociśnięta wypalarką, obrócona na kolejną ściankę do obrobienia i wypuszczona. Maszyny po prostu wykonują zadane wcześniej polecenia, przesuwają się tam gdzie im kazano, a to czy obiekt kostki trafi do podajnika nie znajduje się na liście ich problemów.
Jak mogliście się domyślić, każda osoba czy każde ramię maszyny to osobny wątek – w OOP musimy bardzo uważać żeby wątki się nie poplątały, stosować synchronizowane bloki, „atomowe” zmienne, dodatkowe warunki itp. Tymczasem FP jest z natury przyjazne pracy w środowisku wielowątkowym. Zobaczmy to na prostym przykładzie w Javie:

Kod nie jest wymagający: mamy tutaj liczbę, która przyjęła postać obiektu typu „ObjectProgramming” który charakteryzuje się tylko tą wartością liczbową wstrzykniętą do konstruktora. Dostajemy również metodę „increment(int anyNumber)” która zwiększa wartość naszej jedynej zmiennej o nową wartość.
Z drugiej strony mamy klasę „FunctionalProgramming” która nie posiada żadnych swoich zmiennych, a jedynie metodę „sum(int number1, int number2)„. Jeśli użyjemy tych metod jednokrotnie z tymi samymi zmiennymi to wynik zawsze będzie ten sam:
ObjectProgramming oop = new ObjectProgramming(0);
oop.increment(1);
int a = oop.value;
int b = FunctionalProgramming.sum(0,1);
a == b;
Kiedy jednak operacje zostaną powtórzone, to widzimy, że obiekt „oop” zachowuje swój stan, natomiast funkcja sum() wciąż zwraca to samo. „Akcja” zaczyna się jednak dopiero gdy wpuścimy na arenę dodatkowe wątki, które będą ze sobą konkurować o dostęp do obiektu i próbować łapać go jeszcze zanim ich poprzednik ukończył swoją „obróbkę”;

Ale czy każdy program można pisać samymi funkcjami?
Biorąc na tapet mój wcześniejszy projekt „ITCandidateEvaluator”, teoretycznie byłoby to możliwe. Tam wszystko obraca się wokół kandydatów, a dokładnie ich obiektów opisywanych takimi zmiennymi jak imię, wiek, data ewaluacji czy wynik. Podsumowując: same litery i cyfry. Umieszczając je w bazie danych i dokonując odpowiednich zapytań SQL-owych, inkrementacji i konkatenacji mógłbym sprawić, że klasa Kandydat stałaby się zbędna i została zastąpiona jakąś klasą typu Helper, bez zmiennych, za to ze statycznymi metodami przeliczającymi dane i aktualizującymi rekordy w bazie danych.
Pytanie: Co bym w ten sposób zyskał?
Cóż, bezpieczną pracę wielowątkową.. która w tym projekcie była mi jak psu na budę. Zbędna. Za to straciłbym kupę czasu na wymyślanie koła na nowo, kod byłby trudny do zrozumienia, wyjątkowo nieintuicyjny.
Programując maszyny CNC takie jak tokarki, frezarki, czy bardziej nowocześnie: drukarki 3D musimy myśleć funkcyjnie: ten element przesuń na pozycję (x,y,z) na czas t, następnie tamten element przesuwaj do góry po osi pionowej przy prędkości ustawionej na v, itd. To jaki obiekt właśnie obrabiamy nie musi być uwzględniane w kodzie.
Przeciwieństwem powyższego niech będzie gamedev, gdzie pisanie kodu fuknkcyjnie byłoby prostą drogą do obłędu. Jeszcze platformery z ludzikami z patyczków biegnącymi do przodu jakoś by się dało zakodować. Ale coś poważniejszego? Bronie, czary, postacie niezależne, elementy otoczenia, interfejs… to wszystko to są obiekty, które mają jakiś stan i opisujące go zmienne.
Podsumowując
Próbując pójść na zdrowy kompromis dokopujemy się do elementów programowania funkcyjnego wprowadzonego w Javie, wersji ósmej z 2014 roku. Lambdy, interfejsy funkcyjne, referencje metod, wreszcie strumienie.
Z wszystkimi tymi rzeczami miałem już styczność, ale do ich zmasterowania jeszcze mi daleko. Stąd pomysł na ten mini cykl gdzie po kolei – od przedstawienia paradygmatu w niniejszym tekście, aż po Stream API przekopiemy się szukając zrozumienia i nabrania wprawy w użytkowaniu owych narzędzi.
Bo temat nie jest łatwy.
Łatwo, to jest posłuchać Intellij’a i kliknąć „zamień na wyrażenie lambda” kiedy ci podpowiada w podobny sposób co ja swojej córce, która zapomniała do kwiatka dorysować listków. Tak to każdy potrafi. To, zaś co odróżnia profesjonalistę od „każdego”, to świadome korzystanie z wybranego narzędzia, z konkretnego powodu.

Jeśli, tak jak ja, też chcecie być profesjonalistami to zapraszam do kolejnego artykułu, poświęconego lambdzie, gdzie nauczymy się samodzielnie (i świadomie!) wstawiać „strzałeczki”.
To, co? Strzałeczka i do zobaczenia w następnym odcinku!