Lambda Days 2017 – czego tu nie było…

Tegoroczna edycja Lambda Days pokazała, że programowanie funkcyjne ściąga pod swoje skrzydła coraz większą rzeszę programistów. Ponad 400 uczestników, trzy ścieżki, dwa dni. To jest już coś. W tej relacji nie chcę omawiać poszczególnych wystąpień. Klasyczna formuła ta nie do końca pasuje do mojego uczestnictwa w konferencji. W zamian pozwolę sobie na podzielenie relacji według tematyki.

Ścieżka historyczna

Trzy prezentacje poświęcone historii. John Hughes i Mary Sheeran pokazali, w jaki sposób programowanie funkcyjne wpływało na rozwój inżynierii oprogramowania. Jak zazębiały się pewne koncepcje naukowe i praktyka rozwoju oprogramowania. Następnie Sydney Padua omówiła historię Ady Lovelace i Charlesa Babbage. Było to o tyle ciekawe, że Sydney jest grafikiem, a nie programistą. Jest też autorką serii komiksów o Lady Adzie. Drugiego dnia David Turner opowiedział nam historię języków funkcyjnych. Cały wykład był poprowadzony w ciekawy sposób. Słuchając go, można było zrozumieć decyzje kolejnych pokoleń programistów.

Dwie poświęcone wpływowi inżynierii na historię. Heather Miller, która pokazała, w jaki sposób ruch open source zmienił nasze postrzeganie świata. A Brian L. Troutwine w keynocie otwierającym drugi dzień konferencji omówił, jak polityka ma wpływ na decyzje podejmowane w projektach. I jakie są tego konsekwencje.

Ścieżka Elixira

Najpierw trafiłem na prezentację Jose Valima o GenStage i Flow. Prezentacja bardzo fajna, ale zabrakło mi trochę fajerwerków. Następnie Wojciech Turek mówił o symulacji ruchu samochodowego z wykorzystaniem Erlanga. Całość na superkomputerze Prometeusz. Drugiego dnia Tomek Kowal pokazał nam, jak używa Elixira w aplikacji finansowej.

Cała reszta, czyli wypełniacze

Wypełniaczy pierwszego dnia dużo nie było. Troels Henriksen omówił krótko Futhark. Język programowania współbieżnego dla kart graficznych. Następnie Annette Bieniusa zrobiła wprowadzenie do haskellowego BPMa. Serio, to był BPM, który wykorzystywał system typów z Haskella. Drugi dzień to troll prezentacja Jonas Winje i Einar Høsta o praktycznym rachunku lambda z wykorzystaniem Emaca. Kontr-troll z publiczności już w pierwszym pytaniu – a co z użytkownikami vim? Następnie Paweł Szulc zrobił wprowadzenie do schematów rekursji w scali. A całość zakończyłem prezentacją Roba Martina. W sumie nie wiem o czym, bo prezentacja zaczęła się nieźle, ale potem był chaos.

Podsumowanie

Konferencja była całkiem udana. Tradycyjnie odstałem w kolejce po paszę, ale ta była super smaczna. A za rok wyślę coś o elixirze 🙂

Seksizm z certyfikatem koszerności

Była sobie aferka z kalendarzem Sii. Była i przebrzmiała. Znajome z IT albo nie widziały w tym nic złego, albo wręcz przeciwnie. Widziały w tym całe spektrum opresji. Ta druga grupa dodatkowo twierdziła, że ta pierwsza grupa jest ślepa. Padło też określenie SeksizmW. Rzecz w tym, że część pań ma mentalność Mei (jak uważałeś w szkole, to wiesz, o kogo chodzi).

W dużym skrócie – jak naszym zdaniem ktoś robi coś, czego mu zazdrościmy, to będzie to seksizm i dyskryminacja. Na przykład ładniejsza koleżanka trafiła na okładkę firmowego kalendarza, a ja się nawet nie zgłosiłam do tej zabawy. Chuj, że koleżanka sama chciała. Dobrze się bawiła i było super. Swoją zazdrość co do jej sukcesu, przeniosę na organizatora/pomysłodawcę/jakiegokolwiek samca związanego z danym wydarzeniem.

Jeżeli jednak ktoś jawnie dyskryminuje ze względu na płeć, tak że mi kobiecie jest lepiej, to nie jest to seksizm. Takie zachowanie jest OK, otrzymać może certyfikat koszerności.

Dlaczego o tym piszę? Ponieważ właśnie w ten sposób postępują organizatorzy WebSummit w Lizbonie. Za samo to, że jesteś kobietą, otrzymujesz 90% zniżki. Hm… gdzie się podziały wszystkie obrończynie równości ze względu na płeć?

Swoją drogą ciekawe co by się stało, jakbym się tam zarejestrował. Albo zrobił taką promocję dla panów na jakiejś konferencji. Ciekawe…

ps. post nie trafia do kategorii programowanie, ponieważ nie chcę zaśmiecać maila z jvm-bloggers. Choć może powinienem. Powinienem.

Kotlin z JPA – rzeczy nieoczywiste

Zgodnie z tradycją kończąc szkolenie, pokazuję coś ekstra. Dziś tym czymś ekstra była implementacja prościutkiego silnika blogowego. Samo zadanie jest „egzaminem końcowym” kursu JPA. Nie tworzymy niczego ambitnego, bo mamy tylko 4 godziny na to zadanie. Uczestnicy mają samodzielnie przygotować klasy, skonfigurować zależności i odpalić całość. Moja wersja różniła się jednak od tej, którą mieli wykonać uczestnicy. Ja pisałem w Kotlinie.

Wprowadzenie

Na stronie Springa jest bardzo krótkie wprowadzenie do pracy z Kotlinem i JPA. Po jego lekturze możemy czuć się „mocni”, bo tak naprawdę nie ma tam nic odkrywczego. Nie do końca.

Nasz model danych składać się będzie z kilku klas:

  • Author – reprezentuje autora bloga. Taki debilny value object.
  • Blog – będzie zawierać nazwę bloga oraz autora.
  • BlogPost – pojedynczy post zawierający datę, treść, odwołanie do bloga.

Do tego mamy jeszcze klasę DomainObject, w której zdefiniujemy zasady nadawania id oraz wersji. Prościej się nie da.

Dodatkowo chciałem pokazać użycie REST Repositories, których opis znajdziecie tu oraz Actuatora, o którym poczytacie tu. Warstwę prezentacji na chwilę obecną olejmy, zrobi się później za pomocą Angulara czy innego Reacta. Przy czym w tym poście skupię się na elementach związanych encjami.

Aplikacja ma pozwalać na zarządzanie autorami, blogami i postami. Wykorzystując REST Repositories, nie będziemy musieli dużo pisać. Podstawowe operacje są już zmapowane na odpowiednie metody HTTP. Dodatkowo oczekujemy od niej, że będzie można wykonywać proste zapytania wykorzystujące Criteria API. Będziemy musieli zatem dopisać jakiś prościutki serwis, który nam to umożliwi.

Zaczynamy

Używając Spring Initializr, czy to z poziomu IDE, czy to za pośrednictwem strony wygenerowałem pakiet startowy. Projekt jest mavenowy. Po zaimportowaniu do Idei spróbowałem go uruchomić i od razu mała niespodzianka:

Listing 1. Co my tu mamy…

Exception in thread "main" java.lang.NoSuchMethodException: com.luxoft.jva014.blog.Jva014BlogApplication.main([Ljava.lang.String;)
	at java.lang.Class.getMethod(Class.java:1786)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:126)

Problem, który tu mamy nie wynika z błędu w konfiguracji startera. Ma on swoje źródło w sposobie, w jaki działa Idea. Rzućmy okiem na kod:

Listing 2. Konfiguracja startowa

@SpringBootApplication
class Jva014BlogApplication

fun main(args: Array<String>) {
    SpringApplication.run(Jva014BlogApplication::class.java, *args)
}

Rzecz w tym, że Idea będzie chciała uruchomić klasę z adnotacją @SpringBootApplication. Klasa ta nie posiada metody main, ponieważ ta jest zdefiniowana jako funkcja. Jest to absolutnie prawidłowe zachowanie, ponieważ Kotlin zamiast metod statycznych wprowadza funkcje „globalne”. Funkcja main w wyniku kompilacji trafi do klasy Jva014BlogApplicationKt. Zatem musimy uruchamiać tę klasę. Nie pozostaje nam zatem nic innego jak zgłosić buga. Nie jest to jednak problem, ale raczej ciekawostka.

Jak już uruchomimy naszą aplikację, to mając na pokładzie Actuatora, możemy sprawdzić jej stan. Wystarczy otworzyć localhost:8080/health i bangla.

Encje

Czas coś zaimplementować. Zacznijmy zabawę od DomainObject, która zawiera id i wersję. Implementacja jest banalnie prosta:

Listing 3. Implementacja DomainObject

@MappedSuperclass
class DomainObject(
        @Id var id: UUID = UUID.randomUUID(),
        @Version var version: Int = 0
)

Pominąłem implementację hashCode i equlas, ale ta jest. Pozostałe encje wyglądają podobnie:

Listing 4. Pozostałe encje modelu

@Entity
@AttributeOverrides(
        AttributeOverride(name = "id", column = Column(name = "AUTHOR_ID")),
        AttributeOverride(name = "version", column = Column(name = "AUTHOR_VERSION"))
)
@Table(name = "AUTHOR")
class Author(var name: String = "") : DomainObject()

@Entity
@NamedQuery(name = "findAll", query = "select B from Blog B order by B.name")
@AttributeOverrides(
        AttributeOverride(name = "id", column = Column(name = "BLOG_ID")),
        AttributeOverride(name = "version", column = Column(name = "BLOG_VERSION"))
)
@Table(name = "BLOG")
class Blog(

        @Column(name = "BLOG_NAME")
        val name: String = "",

        @JoinColumn(name = "BLOG_AUTHOR")
        @OneToOne(cascade = arrayOf(CascadeType.ALL))
        var author: Author = Author(),

        @OneToMany(mappedBy = "blog", fetch = FetchType.EAGER, cascade = arrayOf(CascadeType.ALL))
        var posts: MutableList<BlogPost> = ArrayList<BlogPost>()
) : DomainObject()

@Entity
@NamedQuery(name = "findByBlog", query = "select P from BlogPost P where P.blog.id = :id")
@AttributeOverrides(
        AttributeOverride(name = "id", column = Column(name = "POST_ID")),
        AttributeOverride(name = "version", column = Column(name = "POST_VERSION"))
)
@Table(name = "BLOG_POST")
class BlogPost(
        @Column(name = "POST_DATE")
        var date: Date = Date(),

        @Column(name = "POST_TITLE")
        var title: String = "",

        @Lob
        @Basic(fetch = FetchType.EAGER)
        @Column(name = "POST_TEXT")
        var text: String = "",

        @ManyToOne(fetch = FetchType.EAGER)
        @JoinColumn(name = "BLOG_ID")
        var blog: Blog = Blog()
) : DomainObject()

Uwaga. Wysokie stężenie AttributeOverride wynika z założeń zadania, które mieliśmy wykonać. W rzeczywistości można to olać.

Coś jednak tutaj nie pasuje. Zresztą uważny czytelnik dostrzeże, to już na Listingu 2. Klasy w Kotlinie są domyślnie finalne. Tu musimy omówić kilka kwestii. Kiedyś już pisałem o data class w kontekście JPA. DC choć zacne, to do pracy z JPA średnio się nadają. Z jednej strony finalne, a z drugiej nie mogą nic rozszerzać. Świetnie sprawdzą się jako DTO, ale nie pełnoprawne encje. Tu mamy jednak do czynienia ze zwykłą klasą. Jakim cudem mogę użyć takiej klasy jako niefinalnej? Ano mogę. Z pomocą przychodzi mi plugin do kompilatora o wdzięcznej nazwie allopen. Pozwala on na zdefiniowanie listy adnotacji, których użycie na klasie spowoduje wygenerowanie klasy otwartej. Gdy tworzymy projekt za pomocą springowego inicjalizera, to plugin ten zostanie dodany (pod nazwą kotlin-spring) i skonfigurowany tak, by otwierać klasy, które muszą być niefinalne dla springa. My musimy użyć jeszcze innej wersji tego pluginu pod nazwą kotlin-jpa, ale…

I dupa – czas na przesiadkę na gradle

Problem z JPA i Kotlinem polega na tym, że jeżeli używamy Mavena, to nie mamy od ręki dostępu do wielu ważnych rzeczy. Najistotniejszym brakiem jest brak procesora adnotacji, który potrafiłby poradzić sobie z klasami kotlinowymi. Jest co prawda kapt, ale ten działa tylko z gradlem. Tracimy zatem możliwość automatycznego generowania metamodelu. Rykoszetem dostajemy też w przypadku otwierania klas, bo nie możemy użyć kotlin-jpa, bo nie ma wersji na mavena, a musimy ręcznie skonfigurować wszystko w pom.xml.

Przy czym warto zaznaczyć, że problem ten wynika z niedopasowania narzędzia. Kotlin jest zdecydowanie zorientowany gradlowo i trzeba mieć, to na uwadze.

Mapowania

Tworząc mapowania pomiędzy encjami, nie musimy się ograniczać. Jeżeli tylko mamy podpięty plugin allopen, to pracujemy ze zwykłymi kotlinowymi klasami. Ciekawostką jest to, że allopen otwiera też dc. Jednakże próba kompilacji kodu, w którym dc dziedziczą po sobie, skończy się błędem.

Rzućmy jeszcze raz okiem na encję Blog, ponieważ zawiera ona kilka ciekawostek:

Listing 5. Encja Blog

@Entity
@NamedQuery(name = "findAll", query = "select B from Blog B order by B.name")
@AttributeOverrides(
        AttributeOverride(name = "id", column = Column(name = "BLOG_ID")),
        AttributeOverride(name = "version", column = Column(name = "BLOG_VERSION"))
)
@Table(name = "BLOG")
class Blog(

        @Column(name = "BLOG_NAME")
        val name: String = "",

        @JoinColumn(name = "BLOG_AUTHOR")
        @OneToOne(cascade = arrayOf(CascadeType.ALL))
        var author: Author = Author(),

        @OneToMany(mappedBy = "blog", fetch = FetchType.EAGER, cascade = arrayOf(CascadeType.ALL))
        var posts: MutableList<BlogPost> = ArrayList<BlogPost>()
) : DomainObject()

Pierwszą rzeczą na, którą chciałbym zwrócić uwagę, to wykorzystanie typu Author? zamiast Author. Wynika to z zasady generowania konstruktora domyślnego. Jeżeli wszystkie pola w konstruktorze podstawowym są zainicjowane wartościami domyślnymi, to zostanie wygenerowany konstruktor domyślny. Będzie on publiczny. Po drugie możemy używać zarówno var, jak i val. Przy czym trzeba pamiętać, że pole oznaczone jako val będzie finalne. Po trzecie w przypadku mapowania kolekcji musimy użyć MutableList zamiast List. Kolekcje niezmienne w Kotlinie są co do zasady kowariantne, czyli mówiąc językiem kodu:

Listing 6. Lista niezmienna

@OneToMany(mappedBy = "blog", fetch = FetchType.EAGER, cascade = arrayOf(CascadeType.ALL))
var posts: List<BlogPost> = ArrayList<BlogPost>()

Wygeneruje nam:

Listing 7. Lista niezmienna w bytecodzie

Compiled from "Jva014BlogApplication.kt"
public class pl.koziolekweb.blog.Blog extends pl.koziolekweb.blog.DomainObject {
  //...
  private java.util.List<? extends pl.koziolekweb.blog.BlogPost> posts;
  //...
  public java.util.List<pl.koziolekweb.blog.BlogPost> getPosts();
  public void setPosts(java.util.List<? extends pl.koziolekweb.blog.BlogPost>);
  //...
}

Co przy próbie uruchomienia zakończy się klasycznym błędem ze strony Hibernate:

Listing 8. Lista niezmienna w runtime

Caused by: org.hibernate.AnnotationException: Collection has neither generic type or OneToMany.targetEntity() defined: pl.koziolekweb.blog.Blog.posts
	at org.hibernate.cfg.annotations.CollectionBinder.getCollectionType(CollectionBinder.java:694) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final]
	at org.hibernate.cfg.annotations.CollectionBinder.bind(CollectionBinder.java:488) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final]
	at org.hibernate.cfg.AnnotationBinder.processElementAnnotations(AnnotationBinder.java:2140) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final]
	at org.hibernate.cfg.AnnotationBinder.processIdPropertiesIfNotAlready(AnnotationBinder.java:911) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final]

Oczywiście zmiana kodu z listingu 6 na poniższy:

Listing 9. Lista niezmienna

@OneToMany(mappedBy = "blog", fetch = FetchType.EAGER, cascade = arrayOf(CascadeType.ALL), targetEntity = BlogPost::class)
var posts: List<BlogPost> = ArrayList<BlogPost>()

Nie zmienia nic w bytecodzie, ale za to błąd jest z serii „magicznych”:

Listing 10. Dodanie targetEntity

Caused by: org.hibernate.annotations.common.AssertionFailure: Fail to process type argument in a generic declaration. Member : pl.koziolekweb.blog.Blog#posts Type: class sun.reflect.generics.reflectiveObjects.WildcardTypeImpl
	at org.hibernate.jpa.internal.metamodel.AttributeFactory$PluralAttributeMetadataImpl.getClassFromGenericArgument(AttributeFactory.java:875) ~[hibernate-entitymanager-5.0.11.Final.jar:5.0.11.Final]
	at org.hibernate.jpa.internal.metamodel.AttributeFactory$PluralAttributeMetadataImpl.<init>(AttributeFactory.java:784) ~[hibernate-entitymanager-5.0.11.Final.jar:5.0.11.Final]
	at org.hibernate.jpa.internal.metamodel.AttributeFactory$PluralAttributeMetadataImpl.<init>(AttributeFactory.java:758) ~[hibernate-entitymanager-5.0.11.Final.jar:5.0.11.Final]

Dlaczego? Ponieważ Kotlin wymaga typu KClass (klasy kotlinowej) w miejsce zwykłej javowej dla targetEntity. Jedyną opcją pozostaje zatem użycie MutableList. Swoją drogą jest, to babol samego Kotlina, bo na pałę podkłada własny typ zamiast javowego.

Podsumowanie

Czas na małe podsumowanie. Kotlin z JPA ma sens, ale trzeba zwracać uwagę na pewne pułapki. Na pewno należy używać gradle zamiast mavena. Dzięki temu będziemy mieli lepsze wsparcie ze strony narzędzi około kotlinowych. W tym większy wybór, jeśli chodzi o pluginy kompilatora. Jak pokazuje nam przykład z targetEntity, Kotlin ogranicz nas w pewnych sytuacjach, uniemożliwiając użycie wszystkich elementów JPA.

Za plus należy uznać zwięzłość kodu, którą w Javie uzyskujemy z wykorzystaniem Lomboka.

O czym nie napisałem, ale napiszę, to przede wszystkim REST Repositories i użycie Criteria API. Szczególnie ten drugi temat jest dość rozbudowany, bo język daje nam tu wiele możliwości.

W labiryncie

Mazes for Programmers Cover

Tytuł: Mazes for Programmers Code Your Own Twisty Little Passages
Autor: Jamis Buck
Rok: 2015
ISBN: 978-1-68050-055-4

Dawno, dawno temu był taki serial W LabiryncieW, który uchodzi za pierwszą polską operę mydlaną. W tym samym czasie ja wraz z bratem zagrywaliśmy się w MaziacsW. Grę, która dla nas, gówniaków LVL 5 i LVL4, był na tyle trudna, że wciągała. Jak ktoś chce się spróbować, to może poszukać w necie. Linka nie podam, bo większość wymaga Javy jako appletu. Nie będę promował kiły wśród znajomych.

Zanim jednak zaczniecie szukać w internetach grywalnej wersji, to rzućcie okiem na książkę „Mazes for Programmers Code Your Own Twisty Little Passages” Jamisa Bucka. Książka poświęcona jest algorytmom tworzenia labiryntów na potrzeby gier. Począwszy od prostych, gdzie mamy jedną ścieżkę oraz wzorce, poprzez bardziej złożone, pseudolosowe, aż po labirynty wielopoziomowe. Efektywnie jest to 6 algorytmów, które są modyfikowane w zależności od potrzeb. Całość opisana za pomocą prostego języka, który tylko miejscami wymaga głębszego skupienia. Kod programów jest naklepany w Rubym, co można uznać za rozsądne. Ja z moją zerową znajomością tego języka czytałem kod i rozumiałem, co autor chce przekazać.

Poza generowaniem logicznej struktury labiryntów poznamy tu też tajniki ich rysowania. Książka obejmuje nie tylko labirynty na planie prostokąta, ale też labirynty kołowych czy o określonej grubości ścian. Chyba jednym z najbardziej efektownych jest labirynt na planie Wstęgi MöbiusaW. Jednym z ciekawszych aspektów jest wizualna analiza algorytmów poprzez wprowadzenie kolorowania, które uwypukla schematy działania poszczególnych algorytmów.

Jeśli miałbym się do czegoś przyczepić, to brak rozdziału poświęconego algorytmom rozwiązywania labiryntów. Na potrzeby książki wprowadzono tylko DijkstręW w podstawowej wersji. Trochę mało. Zabolał też uproszczony model składowania danych. Autor ogranicza się do tablic, a o metodzie grafowej tylko wspomina. Z drugiej strony komponując wiedzę z książki z wiedzą z kursu Advanced Data Structures możemy pokusić się o coś bardziej rozbudowanego 🙂

Książka nie jest jakoś super przydatna w codziennej pracy programisty. Z drugiej strony, programowanie to przede wszystkim pasja, hobby i dobra zabawa. Zatem taki przerywnik w smutnym świecie kolejnych CRUDów jest bardzo fany. Na pewno jest to pozycja obowiązkowa dla tych, którzy chcą zająć się pisaniem gier. Duża dawka pojęć, które można przekuć na zapytania do google.

Nie oceniaj człowieka…

Kilka osób miało ból dupy o kalendarz Sii. Sii grzecznie przeprasza. Widać, że odrobili lekcję z zarządzania kryzysem w mediach społecznościowych.

Tyle tylko, że takie przepraszanie nie ma sensu. Powiem więcej ono jest szkodliwe.

Dobra, lecimy z koksem, czyli rozmowa kwalifikacyjna…

Mamy zazwyczaj trzy części.

W pierwszej oceniamy umiejętności twarde. Tu bez zmian.
W drugiej oceniamy umiejętności miękkie. Zgodnie z logiką przepraszania należało by wywalić tą część. Dlaczego? O tym za chwilę
W trzeciej oceniamy finanse, czyli czy poziom kandydata, jego oczekiwania i nasze możliwości pokrywają się na tyle, by go zatrudnić.

Wywalić ocenę osoby

Tyle mówi się o nie ocenianiu ludzi przez pryzmat wyglądu… zatem wyobraź sobie, że na rozmowę przychodzi (odsiew techniczny nastąpił) typowy Maciek. Nasz bohater lekko jebie cebulą i czosnkiem. Mamy zimę, a Maciek dba o zdrowie. Maciek z lekka się lepi i wygląda trochę jak żul. Poza tym miły chłopak, o szerokich zainteresowaniach, nie spędza czasu w piwnicy, ma jakieś tam swoje hobby, oczytany…

Po Maćku przychodzi typowy Leszek. Poziom wiedzy ten sam co Maciek. Tyle tylko, że nie sprawdził by się jako strach na wąpierze i strzygi. Poza tym miły chłopak, o szerokich zainteresowaniach, nie spędza czasu w piwnicy, ma jakieś tam swoje hobby, oczytany…

Którego zatrudnisz?

Maćka czy Leszka?

Zawsze oceniamy ludzi

Pierwsze wrażenie… około 5 sekund. Potem trzeba już wiedzieć, albo mieć tą naturalną umiejętność, jak je podtrzymać. Zapewne zatrudnisz Leszka, bo wywarł lepsze pierwsze wrażenie. Oceniłeś go przez pryzmat wyglądu. W dodatku tej części wyglądu, na którą ma bezpośredni wpływ, a zmiana może być stosunkowo łatwa.

Tu mała dygresja. Ocena na podstawie wyglądu dotyczy też cech, na które wpływu nie mamy np. koloru skóry, dysfunkcji fizycznych, czy pewnych zachowań, które są warunkowane naszym doświadczeniem. Nie mówię o tym aspekcie oceny, bo to zupełnie inna bajka.

Zatem oceniłeś naszych chłopaczków na podstawie wyglądu. Czy tego chcesz czy nie. Zatem podnoszenie larum, że ktoś wydał kalendarz z fotami dziewczyn w IT, to zwyczajna hipokryzja.

Liczy się zabawa

I tylko tyle. Czy kalendarz uprzedmiotawia uczestniczki sesji? Nie, one zapewne dobrze się bawiły i nie czuły się w uprzedmiotowione. Czy kalendarz „szczuje cycem”? Nie, po prostu nie. Równie dobrze można powiedzieć, że praktycznie każdy ubiór poza bardzo luźną burką jest szczuciem cycem. Serio, bo są ludzie, których podnieca widok dłoni, ramion czy łydki.

W czasie takiej sesji liczy się dobra zabawa i tyle. Powiem wam jeszcze jedno, wiele dziewczyn, obiektywnie ładnych i zadbanych, ma za sobą jakieś tam mniejsze czy większe doświadczenia w różnych formach modelingu. Czasami są to pojedyncze sesje „bo będzie fajnie”, czasami pierwsza praca w roli hostessy. I jest trochę większa szansa, że takie właśnie osoby zgłoszą się do firmowej sesji, a jak już się zgłoszą, to dzięki swojemu doświadczeniu będą umiały odpowiednio „podkręcić” zdjęcie.

Dlaczego?

Moim zdaniem ludzie, którzy zarzucają niestosowność takiej formie promocji mają niską samoocenę. Najzwyczajniej w świecie są zazdrośni, że ktoś może zrobić sobie zdjęcie, na którym wygląda super i jednocześnie dobrze się bawić. Dziwnym trafem ta choroba zazdrości, szczególnie o wygląd, zazwyczaj dotyczy dziewczyn. I coś jest w twierdzeniu, że największym wrogiem kobiety jest inna kobieta.

A może jakieś podsumowanie roku?

Trochę na blogu się działo 🙂 Zatem czas na małe podsumowanie.

Z rzeczy blogowych

Przekroczyliśmy 1000 postów, co mnie cieszy, bo po prawie 10 latach prowadzenia bloga mamy całkiem ładny wynik 🙂

W tym roku powstało ponad 100 postów, z czego zdecydowana większość w ramach blogowej wiosny. Cała ta zabawa potwierdziła tylko, że jestem w stanie jeszcze coś napisać 🙂

To, czego nie udało się zrobić, to nowy layout. Jakoś nie mogę znaleźć odpowiedniego, a i z grafikami mi jakoś nie po drodze 🙁
CHUJ! Zmiana nastąpiła.

Z rzeczy technologicznych

Zdecydowanie Kotlin i Elixir. Pierwszy z nich jest dla mnie wyborem numer 2 na JVM, a drugi głównym językiem funkcyjnym. Powiem szczerze, że szczególnie Elixir dał mi dużo w kontekście poszerzania swojej wiedzy i umiejętności.

Po stronie czystej Javy, za największy pozytyw tego roku muszę uznać cykl o Javaslang. Kolejne narzędzie w arsenale 🙂

To, co się nie udało, to rozkmina erlangowego OTP na poziomie pozwalającym na swobodne tworzenie aplikacji. Poległem też, jeśli chodzi o kursy na Courserze.

A co w community?

Na pewno sukcesem była kolejna edycja Chamberconf. Jak już jesteśmy przy konferencjach, to w końcu udało się i wystąpilem na Confiturze 🙂

Community to nie tylko konferencje. Zdecydowanie największym moim sukcesem jest tłumaczenie i rozwój Elixir School. Fajnie jest raz na pewien czas zrobić coś dla zabawy 🙂

Niestety umarł nam, znowu, wrocławski JUG.

Zawodowo i prywatnie

Nowa stara firma i praca trenera, to na pewno największy sukces zwodowy 🙂

A prywatnie? Kolejny release uważam za całkiem udany 🙂

Jak nauczyć się (kolejnego) języka?

Odpowiedź na to pytanie jest prosta – napisać coś w nim.

Tyle tylko, że co mam napisać? Oczywiście najprościej jest naskrobać kolejnego klona Pet Clinic, własny system blogaskowy, albo bibliotekę (taką z książkami). Nie przeczę, są to zacne metody, ale mają pewną wadę. Wymagają od nas znajomości języka i narzędzi na pewnym minimalnym poziomie. Bez tego dość szybko popadniemy w kłopoty i zniechęcimy się po „10 godzinach walki z jakimś mavenem, eclipsem i innym springiem”, gdy chcemy się nauczyć podstaw Javy. Projekty są dobre, ale jako „praca końcowa”. Na początek znacznie lepsze są mniejsze zadania, które pozwalają poznać składnię języka i pobawić się z kodem. Przykładem może być samodzielna implementacja zadań z książki Mazes For Programers. Przykładowy kod jest napisany w Ruby, co samo w sobie może być motywacją do poznania tego języka, ale jest też na tyle prosty i dobrze opisany, że można zaimplementować rozwiązania w innym języku.

Inną drogą jest wzięcie udziału w jakiejś formie rywalizacji. Począwszy od starego dobrego SPOJa, poprzez Project Euler, czy też Top Coder. W tym przypadku mamy dowolnie dużo czasu i nie mamy narzuconej kolejności zadań. Ma to swoje plusy, ale też minusy. Jednym z nich jest brak bata i zachęty do codziennej pracy. Dlatego też warto zainteresować się Advent of Code, która niejako wymusza na nas systematyczną, codzienną pracę.

Te wszystkie metody są doskonałe też wtedy, gdy chcemy nauczyć się kolejnego języka. Najpierw zróbmy rozwiązanie w nowym języku, a następnie w naszym głównym. Zobaczycie różnice.

Na koniec małe przypomnienie. Już 26 listopada będę mówił o Elixirze na InfoMeet, start o 9:30 w sali C. Będzie zabawnie 🙂

„Obiektywne” struktury danych w Elixirze

„Obiektywne”, bo nie „obiektowe”. Przymierzam się do opisania interoperacyjności java-elixir trochę ponad „użyj JInterface”, ale zanim do tego się zabiorę, potrzebujemy mieć kilka dodatkowych narzędzi.

Na początek jednak małe przypomnienie. Już za dwa tygodnie z małym haczykiem tzn. 26 listopada będę mówił o Elixirze na InfoMeet, start o 9:30 w sali C. Będzie zabawnie 🙂

Klasy, obiekty i cała reszta

W językach obiektowych jest prosto. Jeżeli potrzebujemy usystematyzować dane, to definiujemy klasę. Ma ona pola, pola mają wartości. Za czynności odpowiadają metody, które powinny mieć swoje odzwierciedlenie w interfejsach (DDD dla oszczędnych), to tego jest jeszcze element JavaBeans i gotowe. Jak mamy już nasze dane i klasę, to możemy tworzyć obiekty, które reprezentują pewien stan danych. Do tego dochodzą jeszcze kontenery jak na przykład listy, mapy czy sekwencje.

W językach funkcyjnych jest pewien drobny problem…

Podejście funkcyjne

W językach funkcyjnych mamy do dyspozycji m.in. czysto funkcyjne strukturyW, które w uproszczeniu można przyrównać do niezmiennych kolekcji z języków obiektowych (w rzeczywistości najbliżej do tych struktur mają kolekcje z pCollections). Jak jednak odwzorować „klasyczne” obiekty? Zazwyczaj robi się, to poprzez zagnieżdżanie i definiowanie własnych typów.

Na przykładzie Elixira

Osadzanie struktur

Trochę jak w JavaScriptcie 🙂 Czyli nasza złożona struktura to mapa map (o listach, sekwencjach itd. innym razem, ale to nuda jest i tak). Jak wygląda mapa w Elixirze?

Mapy w Elixirze definiuje się w prosty sposób:

Listing 1. Mapa z trzema elementami

iex > mapa = %{:A=> 1, 2=> :b, "C"=> "3"}
%{2 => :b, :A => 1, "C" => "3"}

Jako że Elixir jest słabo typowany, to zarówno klucze, jak i wartości mogą być dowolnego typu. Oczywiście mapy można zagnieżdżać:

Listing 2. Mapa map

iex > mapaMap = %{ "pole" => 1, :pole => mapa}
%{:pole => %{2 => :b, :A => 1, "C" => "3"}, "pole" => 1}

Jest tu jednak pewien „myk”, który będzie bardzo przydatny:

Listing 3. Mapa z kluczami – atomami

iex > mapa = %{:age => 32, :name => "Koziołek"}
%{age: 32, name: "Koziołek"}

Zwróćcie uwagę na, to co stało się z dwukropkami przy nazwach atomów. Zmieniły położenie i z przedrostka stały się przyrostkiem. Technicznie nadal jest, to mapa:

Listing 4. Typ naszej mapy to

iex > i mapa
Term
  %{age: 32, name: "Koziołek"}
Data type
  Map
Reference modules
  Map

I jeszcze jedna zagadka:

Listing 5. Czym jest lista dwuelementowych krotek?

iex > lista = [{:age, 32}, {:name, "Koziołek"}]
[age: 32, name: "Koziołek"]

Lista dwuelementowych krotek, w których pierwszym elementem jest atom, jest tzw. keyword list. Najlepszym odpowiednikiem tego terminu jest lista asocjacyjna. Tak też, to przetłumaczyliśmy w ramach ElixirSchool. W przypadku map jeżeli kluczami są atomy, to Elixir zapewnia spójne z listami asocjacyjnymi API. I jeszcze krótko o typie naszej listy:

Listing 6. Ano jest listą

iex > i lista
Term
  [age: 32, name: "Koziołek}]\n\n"]
Data type
  List
Description
  This is what is referred to as a "keyword list". A keyword list is a list
  of two-element tuples where the first element of each tuple is an atom.
Reference modules
  Keyword, List

Skoro wiemy już, że można zagnieżdżać mapy i możemy budować w ten sposób struktury podobne do JSON-ów, to rzućmy jeszcze okiem na definiowanie własnych typów.

Własne typy

Zanim zajmiemy się typami mała uwaga, własne typy w Elixirze warto definiować w ramach wydzielonego modułu, ponieważ przy bardziej złożonych strukturach będziemy też korzystać z definicji struktur.

Na początek zdefiniujmy sobie moduł User.Struct (można w nazwie, można zagnieździć, dostęp będzie taki sam), który będzie przechowywać informacje o strukturze użytkownika:

Listing 7. Moduł User.Struct

defmodule User do

  defmodule Struct do
      defstruct [age: nil, name: nil]
  end
    
end

Teraz dodajmy do naszej struktury typ, konwencja jest taka, że typ nazywamy t i będziemy się do niego odwoływać przez User.Struct.t (plus jedna metoda do testów):

Listing 8. Definicja typu

defmodule User do

  defmodule Struct do
      defstruct [age: nil, name: nil]

      @type t :: %User.Struct{age: integer, name: String.t}

      @type t(age, name) :: t :: %User.Struct{age: age, name: name}

  end

  @spec printInfo(User.Struct.t) :: nil
  def printInfo(user) do
    IO.puts("User name  is #{user.name} and age is #{user.age}")
  end

end

Mamy tu tak naprawdę dwa typy. t, który jest „właściwym” zdefiniowanym jako mapa pól dla struktury i t(age, name), który rozszerza t oraz pełni funkcję czysto pomocniczą jeżeli chcemy przedefiniować, któryś z elementów struktury. A jak tego używać?

Listing 9. przykładowe użycie

iex > user = %{:age=> 32, :name=>"Koziołek"}
%{age: 32, name: "Koziołek"}
iex > User.printInfo(user)
User name  is Koziołek and age is 32
:ok

Tworzymy sobie naszego użytkownika jako mapę i aplikujemy funkcję. Jeżeli coś popieprzymy, to dostaniemy błąd.

Podsumowanie

Własne typy w Elixirze nie są tak mocne, jak w Javie, ale też nie mamy do czynienia z językiem statycznie typowanym. Warto je jednak wprowadzać i używać, by zachować pewien poziom bezpieczeństwa w naszym kodzie. Miłe…

Jedno z dwojga – Either w akcji

Na początek kilka spraw organizacyjnych. Jak widać pisanie po wiosennym ruchu idzie mi średnio 🙂 Ale się staram. Powodów jest kilka, jeden ma 5 lat, drugi 2 miesiące 🙂 Do tego dość dużo pracy i zdecydowanie za krótka doba.

26 listopada będę mówił o Elixirze na Infomeet. Jak chcecie posłuchać dlaczego moim zdaniem warto, to zapraszam. Slajdy z prezentacji, która odbyła się w ramach targów KarieraIT tutaj. I lecimy z tematem.

Either kto to?

Jednym z problemów związanych ze zwracaniem wyników z funkcji (w rozumieniu zarówno metody obiektu, jak i „właściwej” funkcji) jest „upchnięcie” informacji o wyniku i jego poprawności. Klasycznie można taki kod napisać w następujący sposób:

Listing 1. Klasyczne podejście do problemu

public int sumUpTo(int limit) throws SumException{
	//....
}

Mamy sobie metodę, która rzuca wyjątek, jeżeli coś się jej nie spodoba. Takie rozwiązanie jest OK, lecz ma kilka wad. Po pierwsze wyjątek należy przechwycić. O ile wyjątków niekontrolowanych nie musimy obsługiwać w jawny sposób, to jednak w porządnie napisanym sofcie należy pomyśleć o jakimś mechanizmie do ich przechwytywania. Niestety w klasycznym podejściu walimy blok catch(Throwable) gdzieś wysoko i nam to rybka. Po drugie wyjątki są stosunkowo ciężkim mechanizmem. Wyrzucenie wyjątku kontrolowanego wymaga dodatkowej pracy od JVMki, by odpowiednio posprzątać stan aplikacji bardziej szczegółowy opis tutaj. Po drugie czasami narzucone API ogranicza nasze możliwości, jeśli chodzi o wyjątki i musimy korzystać z konstrukcji przechwyć-opakuj-rzuć. Zazwyczaj zamieniamy wyjątek kontrolowany na ogólny RuntimeException. Po trzecie taka metoda, nawet jak rzuca wyjątek niekontrolowany, to bywa trudnym przypadkiem, gdy chcemy korzystać z kompozycji, czy strumieni. Po prostu napiszemy się tych map i filter, a potem i tak trzeba try-catch, bo coś.

Rozwiązaniem jest użycie Either z biblioteki Javaslang. Jest to interfejs, który posiada dwie implementacje Left i Right. Dodatkowo istnieje konwencja, że poprawny wynik trafia do Right, a błędny do Left. Nie jest ona w żaden sposób weryfikowana, ale API niejako „promuje” użycie Rigth, gdyż funkcje domyślnie korzystają z tej wartości. Można zatem przepisać naszą metodę do:

Listing 2. Zmiana API na korzystające z Either

public Either<String, Integer> sumUpTo(int limit){
	//....
}

Either po co to?

Możemy potraktować Either jako rodzaj kontenera na wynik operacji. Jeżeli zakończy się błędem, to zostanie wypełniona jego lewa strona, w przeciwnym wypadku wypełniona będzie prawa. Bazując na tej informacji, możemy wprowadzić funkcje takie jak map czy filter. Pozwolą one na budowę odpowiedniego przepływu w aplikacji. Przykładowo jeżeli, chcemy wynik naszej funkcji zamienić na komunikat:

Listing 3. Wykorzystanie Either.map

public static void main(String[] args) {
	sumUpTo(10).map(s -> s + "").forEach(System.out::println);
}

Jeżeli z jakiegoś powodu nasza funkcja zwróci błąd, to wynik będzie… pusty. Po prostu mapowanie się nie wykona. Tu uwidacznia się pewna cecha Either. Jest on „stronniczy”, to znaczy gdy z nim pracujemy musimy określić, która strona nas interesuje. Funkcja map pracuje z wartością Right, ale jest też mapLeft. W dodatku mamy jeszcze projekcje…

Projekcje

Projekcje, to nic innego jak opakowane wartości. Dostarczają nam one odpowiednich funkcji w rodzaju map, filter itd., ale już w kontekście odpowiedniej wartości. By się do nich dobrać, należy wywołać right albo left na obiekcie Either. Kod z listingu 3 można zapisać jako:

Listing 4. Wykorzystanie projekcji

public static void main(String[] args) {
	sumUpTo(10).right().map(s -> s + "").forEach(System.out::println);
}

Jawnie opowiadając się po jednej ze stron, możemy skupić się tylko na interesującej nas części wartości albo błędzie.

Podsumowanie

Oczywiście nie przedstawiłem tu wszystkich możliwości Either. Zachęcam do zapoznania się z dokumentacją, kodem oraz samodzielnego eksperymentowania.

Elixir Mix – aktualizacja Hexa

W dużym uproszczeniu Hex jest managerem pakietów w Elixirze. Dokładniej pełni on rolę narzędzia do zarządzania pakietami, zależnościami oraz jest też repozytorium zależności. Coś jak Ivy dla Anta z konfiguracją do repozytorium Mavena. Rzecz w tym, że dziś rzeźbiąc sobie kod otrzymałem błąd:

Listing 1. Błędny błąd

[koziolek@koziolek-laptop learn_elixir (master)]$ mix test
** (UndefinedFunctionError) function Hex.SCM.managers/1 is undefined or private
    Hex.SCM.managers([lock: nil, env: :prod, hex: :exml, build: "/home/koziolek/workspace/learn_elixir/_build/test/lib/exml", dest: "/home/koziolek/workspace/learn_elixir/deps/exml"])
    (mix) lib/mix/dep/loader.ex:173: Mix.Dep.Loader.scm_manager/2
    (mix) lib/mix/dep/loader.ex:44: Mix.Dep.Loader.load/2
    (mix) lib/mix/dep/converger.ex:179: Mix.Dep.Converger.all/9
    (mix) lib/mix/dep/converger.ex:114: Mix.Dep.Converger.all/7
    (mix) lib/mix/dep/converger.ex:99: Mix.Dep.Converger.all/4
    (mix) lib/mix/dep/converger.ex:50: Mix.Dep.Converger.converge/4
    (mix) lib/mix/dep.ex:95: Mix.Dep.loaded/1

Szybkie sprawdzenie mix.exs niby wszystko ok, ale dla pewności dociągnę zależności:

Listing 2. Błędny błąd – bardziej gadatliwy

[koziolek@koziolek-laptop learn_elixir (master)]$ mix deps.get
A new Hex version is available (v0.13.2), please update with `mix local.hex`
Running dependency resolution
** (UndefinedFunctionError) function Access.Map.get_and_update!/3 is undefined (module Access.Map is not available)
    Access.Map.get_and_update!(%Mix.Dep{app: :exml, deps: [], extra: [], from: "/home/koziolek/workspace/learn_elixir/mix.exs", manager: nil, opts: [env: :prod, hex: :exml, build: "/home/koziolek/workspace/learn_elixir/_build/dev/lib/exml", dest: "/home/koziolek/workspace/learn_elixir/deps/exml"], requirement: "~> 0.1.0", scm: Hex.SCM, status: {:unavailable, "/home/koziolek/workspace/learn_elixir/deps/exml"}, top_level: true}, :deps, #Function<11.61161633/1 in Hex.Resolver.attach_dep_and_children/3>)
    lib/hex/resolver.ex:190: Hex.Resolver.attach_dep_and_children/3
    lib/hex/resolver.ex:150: Hex.Resolver.get_deps/4
    lib/hex/resolver.ex:109: Hex.Resolver.activate/5
    lib/hex/resolver.ex:31: Hex.Resolver.resolve/3
    lib/hex/remote_converger.ex:33: Hex.RemoteConverger.converge/2
    (mix) lib/mix/dep/converger.ex:89: Mix.Dep.Converger.all/4
    (mix) lib/mix/dep/converger.ex:50: Mix.Dep.Converger.converge/4

Czyli ok… rzeczywiście robiłem ostatnio update Elixira i trzeba by podbić wersję Hexa! Zatem lecimy z tematem:

Listing 3. Błędny błąd – brak zmian

[koziolek@koziolek-laptop learn_elixir (master)]$ mix local.hex
Are you sure you want to install archive "https://repo.hex.pm/installs/1.3.0/hex-0.13.2.ez"? [Yn] Y
* creating /home/koziolek/.mix/archives/hex-0.13.2
[koziolek@koziolek-laptop learn_elixir (master)]$ mix deps.get
A new Hex version is available (v0.13.2), please update with `mix local.hex`
Running dependency resolution
** (UndefinedFunctionError) function Access.Map.get_and_update!/3 is undefined (module Access.Map is not available)
    Access.Map.get_and_update!(%Mix.Dep{app: :exml, deps: [], extra: [], from: "/home/koziolek/workspace/learn_elixir/mix.exs", manager: nil, opts: [env: :prod, hex: :exml, build: "/home/koziolek/workspace/learn_elixir/_build/dev/lib/exml", dest: "/home/koziolek/workspace/learn_elixir/deps/exml"], requirement: "~> 0.1.0", scm: Hex.SCM, status: {:unavailable, "/home/koziolek/workspace/learn_elixir/deps/exml"}, top_level: true}, :deps, #Function<11.61161633/1 in Hex.Resolver.attach_dep_and_children/3>)
    lib/hex/resolver.ex:190: Hex.Resolver.attach_dep_and_children/3
    lib/hex/resolver.ex:150: Hex.Resolver.get_deps/4
    lib/hex/resolver.ex:109: Hex.Resolver.activate/5
    lib/hex/resolver.ex:31: Hex.Resolver.resolve/3
    lib/hex/remote_converger.ex:33: Hex.RemoteConverger.converge/2
    (mix) lib/mix/dep/converger.ex:89: Mix.Dep.Converger.all/4
    (mix) lib/mix/dep/converger.ex:50: Mix.Dep.Converger.converge/4

No i chuj… Mniej więcej tak to wyglądało:

Życie...

Wychodzi na to, że Hex się jednak nie zaktualizował i nadal wisi w jakiejś przedpotopowej wersji. Nauczony doświadczeniem z Eclipse+Maven wybrałem się do katalogu z repozytorium mixa, czyli ~/.mix/archives/ i wywaliłem wszystko co związane z Hexem, czyli plik hex.ez i katalog hex-0.13.2. Po czym wróciłem do katalogu z projektem i powtórzyłem instalację hexa i ściągnięcie zależności.

Co się okazało?

Ano okazało się, że aktualizacja Hexa nie podmienia pliku hex.ez

Listing 4. Błędny błąd – przyczyna, zwróć uwagę na daty

[koziolek@koziolek-laptop archives]$ ll
razem 392
drwxrwxr-x 3 koziolek koziolek   4096 paź 24 21:07 ./
drwxrwxr-x 3 koziolek koziolek   4096 maj 23  2015 ../
drwxrwxr-x 3 koziolek koziolek   4096 paź 24 21:07 hex-0.13.2/
-rw-rw-r-- 1 koziolek koziolek 262010 maj 19  2015 hex.ez
-rw-rw-r-- 1 koziolek koziolek 123280 maj 23  2015 phoenix_new-0.13.1.ez

Ręczne usunięcie tego pliku wymusza jego ponowne pobranie, już w aktualnej wersji.