sobota, 19 stycznia 2019

Kotlin plus Spring minus Adnotacje

Spring 5 dodał nowy - bardzo wygodny - sposób konfiguracji naszych przy użyciu prostych funkcji STOP Owo podejście nie wymaga stosowania adnotacji ani manipulacji bajtkodem STOP Co więcej Spring dostarcza natywne wsparcie dla Kotlina wykorzystując jego specyficzne mechanizmy, które znacznie upraszczają kod STOP A ponieważ nie jednego flejma o technologiach widziałem to we filozofie brnąć nie będę STOP A Tutaj jak ktoś chce są opisane problemy z adnotacjami The case against annotations STOP

Skoro o kodzie mowa

Rozpoczynając prezentację - dla potrzeb naszego przykładu edukacyjnego stworzymy następująca kolekcję typów, która to kolekcja pozwoli nam zobrazować jak wygląda konfiguracja z użyciem Kotlinowego DSL-a. Jakies interfejsy, kilka 'binów' i działamy :

interface I1
interface I2
class Bean1 : I1
class Bean2(dependency:I1) : I2
class Bean3(dependency:I2)

Aby użyć DSLka wystarczy jeden import :

import org.springframework.context.support.beans

No i już możemy pracować z bardzo - w moim odczuciu - przyjemnym kodem. I w moim osobistym odczuciu to naprawdę jest dosyć czytelna konfiguracja, która jednocześnie jest silnie typowana, czyli kompilator pilnuje nam pleców ,wyłapuje wszelkie babole , troszczy się itd:

  1. beans {
  2. bean<Bean1>()
  3. bean{
  4. val i1:I1=ref<Bean1>()
  5. Bean2(i1)
  6. }
  7. bean("Bean3"){
  8. val i2:I2=ref<Bean2>()
  9. Bean3(i2)
  10. }
  11. }

Powyższy fragment jest standardowym kodem Kotlina - nie ma tam żadnej magii a jedynie wykorzystanie natywnych mechanizmów języka. Deklarujemy nasze trzy beany pobierając zależności poprzez wywołanie metody. Wszystko jest kodem. Wszystko jest w jednym miejscu. Można to ogarnąć łatwo. Chaosu nie ma.

A jeśli nie do końca jeszcze wiadomo to, by lepiej zrozumieć sytuację zerknijmy w głąb metod. Mamy tego MR. beans i wygląda to nawet przyjaźnie ale tenże argument init jakis taki dziwny. Jakieś nawiasy po kropce. Co... co się tutaj stanęło?

  1. fun beans(init: BeanDefinitionDsl.() -> Unit): BeanDefinitionDsl {
  2. val beans = BeanDefinitionDsl()
  3. beans.init()
  4. return beans
  5. }

Otóż cała zabawa z tym DSLem jest możliwa dzięki zapisowi “init: BeanDefinitionDsl.() -> Unit” , który definiuje BeanDefinitionDsl jako odbiorcę wywołań w lambdzie (z engielska 'receiver'). Czyli dla przykładu we fragmencie

  1. beans {
  2. bean<Bean1>()
  3. ...
  4. }

Mamy 'implicit this' (dokładnie tak!) i tak naprawdę, wywołujemy

  1. beans { dsl ->
  2. dsl.bean<Bean1>()
  3. ...
  4. }

Teraz pojedynczy 'bean'. Tutaj pojawiają się dziwne nowe słowa:

  1. inline fun <reified T : Any> bean(....){....}

W tej metodzie dzieje się trochę więcej. Generalnie para słów kluczowych “inline” i “reified” w sprytny sposób zachowa informacje o typie z generyka w 'Runtime', dzięki czemu możliwe będzie utworzenie nowej instancji klasy Bean1 podanej w generyku. Podobna mechanika kryje się za metodą “ref”, która zwraca nam referencję do już zadeklarowanych beanów.

  1. inline fun <reified T : Any> ref(name: String? = null) : T =
  2. when (name) {
  3. null -> context.getBean(T::class.java)
  4. else -> context.getBean(name, T::class.java)
  5. }

Zauważ drogi czytelniku/czytelniczko iż zapis “T::class.java” robi coś niesamowitego i wyciąga klasę z generyka. Magia, po prostu magia….

Chociaż nie. Nie magia a nauka.

Inicjalizacja

Caly mechanizm "beans" to tak naprawdę zwykły wzorzec builder i tak jak na klasycznym buiderze wołaliśmy “build()” lub coś podobnego na sam koniec by zapieczętować budowę - tak tutaj przekazujemy do budowy springowy kontekst. Trochę to wygląda na cykliczną zależność ale w zasadzie nie zauważyłem jeszcze z tym problemów.

  1. val ourInit: BeanDefinitionDsl =beans {...}
  2. GenericApplicationContext {
  3. ourInit.initialize(this)
  4. refresh()
  5. }

I kontekst springowy zainicjalizowany naszymi beanami. Żadnych adnotacji nie trzeba, żadnego CGLiba i majstrowania przy bajtkodzie też nie trzeba!!! Ale to nie koniec. Kotlin i Spring 5 dają nam jeszcze więcej udogodnień bo teraz
skupcie się weźta
wystawimy resta

konfiguracja REST - uważaj na przykłady Javy!

Przede wszystkim czytając tutoriale do nowego mechanizmu - jak na przykład ten https://spring.io/blog/2016/09/22/new-in-spring-5-functional-web-framework - uważaj na użytą składnie Javy, gdyż używając w identyczny sposób Kotlina możesz napotkać na pewne trudności. Otóż API dla Javy zaleca użycie funkcji “route”, która jest zdefiniowana w sposób następujący

  1. public static <T extends ServerResponse> RouterFunction<T> route(
  2. RequestPredicate predicate, HandlerFunction<T> handlerFunction) {
  3.  
  4. return new DefaultRouterFunction<>(predicate, handlerFunction);
  5. }
  6.  

Gdzie "HandlerFunction" to prosty interfejs funkcyjny:

  1. @FunctionalInterface
  2. public interface HandlerFunction<T extends ServerResponse> {
  3. Mono<T> handle(ServerRequest request);
  4. }

Używając tego API możemy wygodnie wpisać sobie lambdę tam gdzie oczekiwany jest HandlerFunction a to dlatego, iż Java z automatu konwertuje ową lambdę na tzw. SAM czyli klasę abstrakcyjną/interfejs z jedną metodą. Dzięki temu możemy sobie napisać route(GET(“/”),r->...) . Niestety w Kotlinie przy konwersji lambdy na typ Javy jest potrzebna dodatkowa podpowiedź dla kompilatora:

  1. //route(GET("/test"), { r -> ok().body(Mono.just("response")) }) <- to nie pyknie
  2.  
  3. //poniżej podajemy podpowiedź, że ta lambda to HandlerFunction i działa
  4. route(GET("/test"), HandlerFunction { r -> ok().body(Mono.just("response")) })

Dlatego też warto używać dedykowany DSL -wiecie taki natywny ze springa a nie żadny na boku robiony - napisany specjalnie dla Kotlina. Konstrukcja tam użyta jest podobna do tego co widzieliśmy w poprzednim odcinku dla definicji Beanów - czyli dla przypomnienia:

  1. fun beans(init: BeanDefinitionDsl.() -> Unit): BeanDefinitionDsl {
  2. val beans = BeanDefinitionDsl()
  3. beans.init()
  4. return beans
  5. }

W przypadku definiowania “routingu” wygląda to bardzo ale to bardzo podobnie :

  1. fun router(routes: RouterFunctionDsl.() -> Unit) = RouterFunctionDsl().apply(routes).router()
  2.  
  3. router {
  4. GET("/hello") { _ ->
  5. ok().body(just("Hello World!"), String::class.java)
  6. }
  7. }

Jak możecie chyba zgadnąć GET to nic innego jak wywołanie metody na naszym DSLu i aby było jawne, jasne i klarowne co tam się dzieje to dzieję coś takiego :

  1. router {
  2. val dsl:RouterFunctionDsl = this //<- o tutaj cała magia
  3. dsl.GET("/hello") { _ ->
  4. //and below is HandlerFunction
  5. ok().body(just("Hello World!"), String::class.java)
  6. }
  7. }

W powyższym przykładzie GET jest po prostu wygodnym opakowaniem na to API dla Javy, o którym mówiliśmy na samym początku :

  1. fun GET(pattern: String, f: (ServerRequest) -> Mono<ServerResponse>) {
  2. routes += RouterFunctions.route(RequestPredicates.GET(pattern),
  3. HandlerFunction { f(it) })
  4. }
  5.  
  6.  

I problem “bojlerplejtu” rozwiązany.

Mono

Zwróć uwagę na sygnaturę funkcji przekazanej do Handlera :

f: (ServerRequest) -> Mono

Czym jest to Mono i jak wpływa na przetwarzanie requestu? O tym w innym odcinku...

poniedziałek, 4 czerwca 2018

Domenowo ale bez alokacji

Ten artykuł miał być początkowo wzmianką o ciekawym - acz niszowym mechanizmie - który umożliwia stosowanie pattern matchingu dla prostych typów bez alokacji niepotrzebnych obiektów na stercie. Jednakże w trakcie pisania i zabawy z javap -c ... okazało się, że tak w sumie to ja mam niekompletne pojęcie co się w wielu miejscach wyplutego z programu scali bytekodu JVM dzieje. Stąd też będzie to bardziej ogólny wpis o tym co produkuje kompilator i kiedy płacimy - a kiedy nie - dodatkową alokacją pamięci.

"Name based extractors"

Czasem dobrze zajrzeć do obcego kodu (ale tylko wtedy gdy pisał go ktoś faktycznie znający się na rzeczy) by odnaleźć warte przestudiowania konstrukcje. I taka konstrukcja pojawia się w Akka typed. Wertując bebechy Akki typowanej natrafimy na poniższe :

/**
 * INTERNAL API
 * Represents optional values similar to `scala.Option`, but
 * as a value class to avoid allocations.
 *
 * Note that it can be used in pattern matching without allocations
 * because it has name based extractor using methods `isEmpty` and `get`.
 * See https://hseeberger.wordpress.com/2013/10/04/name-based-extractors-in-scala-2-11/
 */
private[akka] final class OptionVal[+A >: Null](val x: A) extends AnyVal {

To jest bardzo interesujące. Zawsze wydawało mi się ,iż AnyVal i PatternMatching wzajemnie się wykluczają gdyż stoi jak wał w oficjalnej dokumentacji :


A value class is actually instantiated when:
  1. a value class is treated as another type.
  2. a value class is assigned to an array.
  3. doing runtime type tests, such as pattern matching.

Wrócimy do tego przykładu ale najpierw krótka wycieczka po alokacji obiektów w scali by lepiej zrozumieć co się dzieje gdy do pracy ruszy kompilator. I tutaj na potrzeby edukacji załóżmy, że chcemy mieć taki domenowy typ dla Miesiąca. Będzie bardziej domenowo i biznesowo. Miesiąc jest podzbiorem inta gdzie kilka wartości ma sens a reszta już niekoniecznie.

class Month(private val n:Int) extends AnyVal{
  override def toString: String = s"Month($n)"
}

W teorii rozszerzenie AnyVal powinno zapobiec alokacji nowego obiektu na stercie czym zajmie się kompilator zastępując nasz domenowy Month zwykłym intem w trakcie kompilacji. Niestety ta reguła ma wiele wyjątków i czasem alokacja następuje wbrew naszym oczekiwaniom. Zejdziemy teraz dużo niżej by lepiej zrozumieć kiedy coś takiego następuje..

Poligon

Naszym Poligonem laboratoryjnym będzie domenowy Kalendarz, który operuje na domenowym Miesiącu (wszystko takie domenowe)

class Calendar{
  def somethingWithDate(m:Month) = {
    println(m)
  }
}

I po dekompilacji.

Compiled from "Poligon.scala"
public class jug.workshops.reactive.akka.typed.Calendar {
  public void somethingWithDate(int);
  public jug.workshops.reactive.akka.typed.Calendar();
}

Na pozór kod po dekompilacji wygląda ok, zamiast Month mamy Int i wydawać by się mogło, że wszystko idzie zgodnie z planem ale czeka nas niemiła niespodzianka...

Trochę mechaniki

Gdy zanurkujemy do asemblera to zobaczymy :

  Code:
       0: getstatic     #17                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
       3: new           #19                 // class jug/workshops/reactive/akka/typed/Month
       6: dup
       7: iload_1
       8: invokespecial #22                 // Method jug/workshops/reactive/akka/typed/Month."<init>":(I)V
      11: invokevirtual #26                 // Method scala/Predef$.println:(Ljava/lang/Object;)V
      14: return

Gdzie w linijce 3 pojawia się new czyli tworzenie nowego obiektu. Jest to zapewne Kochani moi polimorfizm w akcji gdzie println gdzie pod spodem musi być wywołane toString ale ze względu na dziedziczenie nie jest jasne z której klasy i tam cała ta maszyneria idzie w ruch. Bardzo łatwo pozbyć się alokacji usuwając polimorfizm ze sceny.

class Month(private val n:Int) extends AnyVal {
  def display: String = s"Month : $n"
}

class Calendar{
  def somethingWithDate(m:Month) = {
    println(m.display)
  }
}


 public void somethingWithDate(int);
    Code:
       0: getstatic     #17                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
       3: getstatic     #22                 
// Field jug/workshops/reactive/akka/typed/Month$.MODULE$:Ljug/workshops/reactive/akka/typed/Month$;
       6: iload_1
       7: invokevirtual #26                 
// Method jug/workshops/reactive/akka/typed/Month$.display$extension:(I)Ljava/lang/String;
      10: invokevirtual #30                 
// Method scala/Predef$.println:(Ljava/lang/Object;)V
      13: return

Po tej wycieczce po alokacjach wracamy do pattern matchingu.

Pattern Matching bez kosztu

Pattern matching bazuje na dekompozycji obiektów i metodzie unapply, która jest takim lustrzanym odbiciem kompozycji i metody apply. Kompilator tutaj dużo działa w tle bo gdy napiszemy poniższą linijkę.

case class Day(v:Int)

No i każdy początkujący adept scali pewnie wie, że będzie z automatu wygenerowany comanion object, ktory ma 40000 metod a wśród nich unapply do pattern matchingu. Ten unapply ma tę wadę, że tak trochę niepotrzebnie za każdym razem Optiona tworzy który zaraz idzie do kosza.

public final class jug.workshops.reactive.akka.typed.Day$ extends ... {
...
  public scala.Option<java.lang.Object> unapply(jug.workshops.reactive.akka.typed.Day);
...
  }

I tutaj pierwsza niespodzianka dla mnie bo okazuje się, że jednak ta metoda przy case class wcale nie jest używana!!!

Według :

However, when we get to pattern matching (§8.1), case classes have their own section on matching, §8.1.6, which specifies their behaviour in pattern matching based on the parameters to the constructor, without any reference to the already-generated unapply/unapplySeq:

Oraz:

I can tell you that even though the compiler generates an unapply method for case classes, when it pattern matches on a case class it does not use that unapply method

No i faktycznie żadnego unapply w asemblerze nie widać. Dopiero gdy zmienimy case class na zwykłą klasę to unapply pójdzie w ruch z alokacją Optiona.

class Day(val v:Int)

object Day{
  def unapply(arg: Day): Option[Int] = Some(arg.v)
}
I asembler :
63: invokevirtual #45                 
// Method jug/workshops/reactive/akka/typed/Day$.unapply:(Ljug/workshops/reactive/akka/typed/Day;)Lscala/Option;


 public scala.Option<java.lang.Object> unapply(jug.workshops.reactive.akka.typed.Day);
    Code:
       0: new           #17                 // class scala/Some
       3: dup
       4: aload_1
       5: invokevirtual #23                 
// Method jug/workshops/reactive/akka/typed/Day.v:()I
       8: invokestatic  #29                 
// Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
      11: invokespecial #32                 
// Method scala/Some."<init>":(Ljava/lang/Object;)V
      14: areturn

Ale po co tam option?

Odsuwając ideologie programowania w kont - z punktu widzenia pattern matchingu to option trochę tak z ch*ja jest. A to dlatego, że jeśli o sam Pattern Matching chodzi to my jako programiści tego Optiona nie tykamy, nie widzimy, nie wykorzystujemy - to je taka wewnętrzna rzecz - i w złym świecie programowania można to opykać bez alokacji na parze wartość/null - pewnie usuną z czatu monadowego ale garbage collector powinien chodzić trochę lepiej - co kto lubi i potrzebuje emocjonalnie w życiu.

Zastosowanie

Jak użycie tego wygląda w Akka (Acce) ? W ActorContextImpl (ku*wa lata uczenia młodzieży by nie dawała w nazwach Impl i ch** wszystko strzelił) znajdziemy

 private var messageAdapterRef: OptionVal[ActorRef[Any]] = OptionVal.None

I dalej mamy pattern matching

val ref = messageAdapterRef match {
case OptionVal.Some(ref) ⇒ ref.asInstanceOf[ActorRef[U]]
case OptionVal.None ⇒ ...
    }

Tak jak ja to osobiście rozumiem - działają w tym miejscu dwa extractory. Pierwszy bezpośrednio w OptionVal. Ale ale zaczynają dziać się rzeczy ciekawe bo żadnego unapply tam nie ma - znajdziemy dwie metody potrzebne do tego całego "name based extraction".

private[akka] final class OptionVal[+A >: Null](val x: A) extends AnyVal {
...

 def get: A =
    if (x == null) throw new NoSuchElementException("OptionVal.None.get")
    else x

  
  def isEmpty: Boolean =  x == null
...
}

Wracamy do naszego laboratorium by sprawdzić co takiego dzieje się pod spodem. Na początek przypomnijmy co mamy :

class Month(private val n:Int) extends AnyVal {
  def isEmpty:Boolean = n < 0 || n > 12
  def get:Int = n
  def display: String = s"Month : $n"
}

object Month{
  def unapply(v: Int) = new Month(v)
}

Co umożliwi nam poniższe zabawy.

    val Month(n) = 2
    println(n)


    1 match {
      case Month(n) => println(n)
      case _ => println("empty")
    }

    3 match {
      case Month(n) => println(n)
      case _ => println("empty")
    }

Fajnie jest ale teraz zmodyfikujmy nasze unapply by przyjmowało bezpośrednio obiekt domenowy, wtedy będzie bardziej biznesowo i domenowo.

object Month{
  def unapply(v: Month) : Month = v
}

Co już nam daje znak, że Option jako taki z równania został wyłączony. Ale czy na pewno? Jest tylko jeden sposób by sprawdzić.

- kod wysokopoziomowy

def somethingWithDate(m:Month) = m match {
    case Month(n) if n <=6 => println("first half")
    case Month(n)  => println("second half")
    case _ => println("error")
  }

kod (a właściwie kawałek) niskopoziomowy

  public void somethingWithDate(int);
    Code:
       0: iload_1
       1: istore_3
       2: getstatic     #17                 
// Field jug/workshops/reactive/akka/typed/Month$.MODULE$:Ljug/workshops/reactive/akka/typed/Month$;
       5: iload_3
       6: invokevirtual #21                 
// Method jug/workshops/reactive/akka/typed/Month$.unapply:(I)I
       9: istore        4
      11: getstatic     #17                 
// Field jug/workshops/reactive/akka/typed/Month$.MODULE$:Ljug/workshops/reactive/akka/typed/Month$;
      14: iload         4
      16: invokevirtual #25                 
// Method jug/workshops/reactive/akka/typed/Month$.isEmpty$extension:(I)Z
      19: ifne          57
      22: getstatic     #17                 
// Field jug/workshops/reactive/akka/typed/Month$.MODULE$:Ljug/workshops/reactive/akka/typed/Month$;
      25: iload         4
      27: invokevirtual #28                 
// Method jug/workshops/reactive/akka/typed/Month$.get$extension:(I)I
      30: istore        5
      32: iload         5
      34: bipush        6
      36: if_icmpgt     54
      39: getstatic     #33                 
// Field scala/Predef$.MODULE$:Lscala/Predef$;
      42: ldc           #35                 // String first half

I jak to przestudiujecie to nie powinniście tam nigdzie znaleźć żadnego new a jedynie wywołania statyczne.

I wydaje mi się, że to co jest w Acce idzie jeszcze dalej. Przypomnijmy kod :

val ref = messageAdapterRef match {
      case OptionVal.Some(ref) ⇒ ref.asInstanceOf[ActorRef[U]]
      case OptionVal.None

I teraz jeśli chodzi o None to jest to zwykły obiekt z tym isEmpty oraz get.

val None = new OptionVal[Null](null)

I tutaj weź głęboki wdech i przypomnij sobie nasz poprzedni przykład z miesiącem. Dla Pattern Matchingu istotne jest co będzie zwrócone z unapply - żeby to miało get i isEmpty - i tutaj wymijamy unapply i od razu podrzucamy taką instancję singletona.

A co z tym drugim Kejsem? Tutaj mamy ładne customowe unapply.

  object Some {
    def apply[A >: Null](x: A): OptionVal[A] = new OptionVal(x)
    def unapply[A >: Null](x: OptionVal[A]): OptionVal[A] = x
  }

Po co to?

W moim odczuciu AnyVal to w pewnym stopniu udana próba implementacji mechanizmu przeniesienia typów prymitywnych do warstwy domenowej bez zbytniego cierpienia w runtime. Ponieważ koniec końców tam na dole zawsze będzie JVM to często czar pryska i trzeba instancję stworzyć, dlatego też zazwyczaj używałem AnyVal jako zabezpieczenia w sygnaturze. Tutaj pojawia się nowa ciekawa możliwość próby zaimplementowania takiego prostego i opartego na prymitywach ADT co widzieliśmy na przykładzie Akki gdzie działa taki Some/None jako nakładka na ActorRef i null.

Pojawiają się takie brzydkie słowa jak var czy null ale może to nie czas by się spuszczać nad programowaniem ideologicznym i pomyśleć, że ci ludzie oszczędzili tak ileś tam pamięci, którą musieli oszczędzić i poszli na piwo.

Podsumowanie

Także drodzy przyjaciele zachęcam do eksperymentów bo jak wspominałem na samym początku i dla mnie to była edukacja. Wrzućcie czasem kawałek kodu, odpalcie "javap -c" i zobaczcie co się tak naprawdę dzieje pod spodem. No a jak potrzebujecie inspiracji - to zerknijcie w jakiś kod pisany przez mądrych ludzi - najlepiej kod który zarabia jakieś pieniądze.

niedziela, 4 lutego 2018

Kotlin + Akka Typed

Ale po co?

Tak dla ćwiczeń.

No ale po co?

O ile w "świecie scali" ten język wydaje się być ściśle powiązany z Akka to jednak w świecie korporacji gdzie management wykształcony na industrialnych praktykach wciąż widzi dodatnią korelację pomiędzy headcountem a powodzeniem projektu - no i własnie tam coś ta scala się tak nie przyjmuje w tych korporacjach. Jest duży opór. A że korpo to hajs to Akka tez ma API Javowe. Ale jak ktoś pisał w scali to to API Javowe boli strasznie. I tutaj pojawia się Kotlin jako kandydat na "lepszą Javę" - i to co mam nadzieję zobaczymy w artykule - Kod napisany w Kotlinie używającym API dla Javy w praktyce wygląda bardzo podobnie do scali używającej API dla Scali!

No a do tego będzie okazja przećwiczyć akka typed bo trochę mnie wkurwia to receive w aktorach, które łyka co wpadnie.

Ale na pewno o to chodzi?

No dobra. Za Kotlina teraz dostaje się więcej lajków. Zbiór fanbojów akki pokrywa się ze zbiorem fanbojów scali w dużym topniu a Kotlin to teraz mobilki i może jacyś fanboje springa także suma tych zbiorów może da 100 000 lajków. Jak będzie 100 000 lajków pod tym postem powstanie część ósma.

Oddziczanie Javy Kotlinem

Przykład w Scali skopiowany z tutoriala

Jako materiał użyjemy intro blog posta z blogu Akki do akka typed https://akka.io/blog/2017/05/05/typed-intro. Jest to ładne wprowadzenie gdzie mamy użyte dwa typy aktorów - jeden taki bardziej obiektowy ma sobie zmienną a drugi (chyba) bardziej funkcyjny generuje z zachowania nowe zachowanie.

Najpierw zobaczymy przyklejone kawałek Scali z dokumentacji a następnie analogiczny kawałek kotlina. Pod obiema próbkami kodu będzie opis/wyjaśnienie co właściwie trzeba było zrobić.


object Greeter1 {
  sealed trait Command
  case object Greet extends Command
  final case class WhoToGreet(who: String) extends Command

  val greeterBehavior: Behavior[Command] =
    Actor.mutable[Command](ctx => new Greeter1)
}

class Greeter1 extends Actor.MutableBehavior[Greeter1.Command] {
  import Greeter1._

  private var greeting = "hello"

  override def onMessage(msg: Command): Behavior[Command] = {
    msg match {
      case WhoToGreet(who) =>
        greeting = s"hello, $who"
      case Greet =>
        println(greeting)
    }
    this
  }
}

Analogiczny przykład z Kotlina

class Greeter1 : MutableBehaviorKT<Greeter1.Protocol.Command>() {

    private var greeting = "hello"

    override fun onMessage(msg: Command): Behavior<Command> {
        when (msg) {
            Command.Greet -> println(greeting)
            is Command.WhoToGreet -> greeting = "hello ${greeting}"
        }
        return this
    }

    companion object Protocol {
        sealed class Command {
            object Greet : Command()
            data class WhoToGreet(val who: String) : Command()
        }

        val greeterBehaviour: Behavior<Command> = Actor.mutable<Command> { ctx -> Greeter1() }
    }
}

Ok jest jedna zaślepka MutableBehaviorKT ale to tylko taki mały adapter by zutylizować(kocham to słowo) Kotlinow when w miejsce Javowych builder - zara wkleję. Jednak najpierw skoncentrujmy się na tym jak podejść do modelowania komend/protokołu/interfejsu aktorów. W scali zazwyczaj komendy idą do companion object bądź do innego object jeśli interfejs obejmuje kilku aktorów. W Kotlinie także możemy stworzyć companion object jednak istnieją pewne ograniczenia ograniczające miejsce wystąpienia do ciała klasy. Jednym się spodoba innym nie.

W każdy razie zerknijmy na poniższe sytuacje.

DZIAŁA:

companion object Protocol {
        sealed class Command {
            object Greet : Command()
            data class WhoToGreet(val who: String) : Command()
        }

        val greeterBehaviour: Behavior<Command> = Actor.mutable<Command> { ctx -> Greeter1() }
}
TEŻ DZIAŁA:
companion object Protocol {
        val greeterBehaviour: Behavior<Command> = Actor.mutable<Command> { ctx -> Greeter1() }
}

sealed class Command
object Greet : Command()
data class WhoToGreet(val who: String) : Command()
NIE DZIAŁA:
companion object Protocol {

   sealed class Command
   object Greet : Command()
   data class WhoToGreet(val who: String) : Command()

   val greeterBehaviour: Behavior<Command> = Actor.mutable<Command> { ctx -> Greeter1() }
}

Nie rozmieniłem jeszcze dokładnie dlaczego ostatnie nie działa ale trzeba zaznaczyć, że ostatni przykład byłby najbardziej intuicyjny dla programisty scali dlatego tez od niego zaczynałem. Generalnie w Kotlinie mam odczucie, że Companion Object został bardziej stworzony z myślą o metodach niż jako taki "całościowy moduł" dlatego też typy danych lepiej grupować na poziomie pakietu.

Co to jest to MutableBehaviorKT ?

A to sobie taki adapterek zrobiłem by nie używać tych "niby-że-mamy-w-javie-pattern-matching" builderów

abstract class MutableBehaviorKT<T> : Actor.MutableBehavior<T>() {

    abstract fun onMessage(msg: T): Behavior<T>

    override fun createReceive(): Actor.Receive<T> = object : Actor.Receive<T> {
        override fun receiveMessage(msg: T): Behavior<T> = onMessage(msg)
        override fun receiveSignal(msg: Signal?): Behavior<T> = this@MutableBehaviorKT
    }
}

I od razu kilka słów wyjaśnienia. To "object : CośTam" to syntax na deklarację anonimowej klasy w Kotlinie. Tutaj nie używam żadnych builderów tylko deleguję receiveMessage do metody, która we właściwym aktorze zaimplementowana być powinna. Po co jest to receiveSignal jeszcze nie wiem.

Jeszcze raz porównując logikę Scala-ScalaAPI & Kotlin-JavaAPI+adapter

SCALA:
 override def onMessage(msg: Command): Behavior[Command] = {
    msg match {
      case WhoToGreet(who) =>
        greeting = s"hello, $who"
      case Greet =>
        println(greeting)
    }
    this
  }
KOTLIN:
  override fun onMessage(msg: Command): Behavior<Command> {
        when (msg) {
            Command.Greet -> println(greeting)
            is Command.WhoToGreet -> greeting = "hello ${greeting}"
        }
        return this
    }

Immutable Aktor Numer Dwa

SCALA CODE COPIED FROM TUTORIAL
object Greeter2 {
  sealed trait Command
  case object Greet extends Command
  final case class WhoToGreet(who: String) extends Command

  val greeterBehavior: Behavior[Command] = greeterBehavior(currentGreeting = "hello")

  private def greeterBehavior(currentGreeting: String): Behavior[Command] =
    Actor.immutable[Command] { (ctx, msg) =>
      msg match {
        case WhoToGreet(who) =>
          greeterBehavior(s"hello, $who")
        case Greet =>
          println(currentGreeting)
          Actor.same
      }
    }
}
KOTLIN ALTERNATIVE FOR JAVA API:
sealed class CommandJava
data class WhoToGreet(val who: String) : CommandJava()
object Greet : CommandJava()


object Greeter2 {

    val greeterBehavior: Behavior<CommandJava> = greeterBehaviour(currentGreeting = "hello")

    private fun greeterBehaviour(currentGreeting: String): Behavior<CommandJava> =
            Actor.immutable<CommandJava> { _, msg ->
                when (msg) {
                    Greet -> {
                        println(currentGreeting)
                        Actor.same()
                    }
                    is WhoToGreet -> greeterBehaviour("hello ${msg.who}")

                }
            }
}

Tutaj zdecydowałem się wynieść komendy poza object z powodów opisywanych w poprzedniej cześć. Dodatkowo w przypadku Kotlina pojawia się jeden zestaw klamerek więcej gdyż when w przypadku gdy za strzałka występuje więcej niż jedna instrykcja wymaga własnie zamknięcia ich w klamerki podczas gdy scalowe match-case po prostu zgarnia wszystko od strzałki do strzałki. Ale tak poza tym wygląda bardzo podobnie.

We to uruchom

Tutaj też dosyć "scalowo" to wygląda.

 val root: Behavior<Nothing> = Actor.deferred<Nothing> { ctx ->
        val greeter: ActorRef<CommandJava> = ctx.spawn(Greeter2.greeterBehavior, "greeter")

        greeter send WhoToGreet("Java")
        greeter send Greet

        Actor.empty()
    }

ActorSystem.create(root, "HelloWorld")

Ale troszeczkę tutaj oszukuję. Generalnie w Scali wysłanie wiadomości do aktora wygląda tak : actor ! msg ale w Javie/Kotlinie nie można stworzyć takiej metody. Dodatkowo w Kotlinie jeśli chcemy wywołać metodę bez kropki i nawiasów to przy deklaracji trzeba to zaznaczyć operatorem infix. Oczywiście Javowe API tego nie ma i tam trzeba wołać klasycznie actor.tell(msg).

No i ja wtedy wchodzę cały na biało (z extend method):

infix fun <T> ActorRef<T>.send(cmd:T) = this.tell(cmd)

I to działa! Wołanie Kotlinem Javowego API scalowej biblioteki zakończyło się pełnym powodzeniem przyjaciele!

Wołanie Scali z Kotlina

A pojedźmy z eksperymentem trochę dalej i obadajmy jak wyjdzie wołanie bezpośrednio API scalowego z kotlina.

Pierwszy przykład wygląda bardzo podobnie do odpowiednika ze Scali. Nie ma żadnego buildera tylko zwykła metodka onMessage i obyło się bez adapterów.

class HelloScala1 : Actor.MutableBehavior<HelloScala1.Protocol.Command>() {

    private var greeting = "hello"

    override fun onMessage(msg: Command): Behavior<Command> {
        when (msg) {
            Command.ScalaGreet -> println(greeting)
            is Command.ScalaWhoToGreet -> greeting = "hello ${greeting}"
        }
        return this
    }

    companion object Protocol {
        sealed class Command {
            object ScalaGreet : Command()
            data class ScalaWhoToGreet(val who: String) : Command()
        }

        val greeterBehaviour: Behavior<Command> = Actor.mutable<Command> { ctx -> HelloScala1() }
    }
}

A teraz będzie coś fajnego

Ogólnie tutaj tez poszło gładko ale zerknijmy na jeden fragment :

  Actor.immutable { _, msg ->
                when (msg) {
                    is ScalaWhoToGreet ->
                        greeterBehavior("hello, ${msg.who}")
                    ScalaGreet -> {
                        println(currentGreeting)
                        Actor.same<Command>()
                    }

                }
  }

Czy widzicie coś tutaj ciekawego? No kurde... Przecież to jest API Scalowe a kotlinowa funkcja weszła tam jak w masło. Mowa o tym : " Actor.immutable[Command] { (ctx, msg) =>..." , oto co moim zdaniem się dzieje. Od Scali 2.12 Lambdy idą w natywny invokeDynamic toteż Kotlin pewnie widzi zwykłą lambdę z Javy8 - i nawet IDE podpowiada tam Function2. No i teraz poniewaz Kotlin ma filozofię "ma działać wygodnie z Java i chuj", więc lambdy w wywołaniach Javowego API są tłumaczone na SAM types i chyba tak to wyszło. Elegancko.

To we teraz to uruchom

 val root = Actor.deferred<Nothing> { ctx ->
        //scala default parameters not working in kotlin -> props
        val greeter: ActorRef<Command> = ctx.spawn(HelloScala2.greeterBehavior, "greeter", Props.empty())
        greeter.tell(ScalaWhoToGreet("ScalaExample"))
        greeter send ScalaGreet
        Actor.empty()
    }


//    `ActorSystem$`.`MODULE$`.apply()  //pure scala API with default parameters not recognised by Kotlin
    ActorSystem.create(root,"HelloWorld") // create is actually Java API

Tutja zaczęły się trochę schody bo Kotlin nie ma pojęcia o Scalowym cukrze składniowym także zapomnij o Object.apply i scalowych parametrach defaultowych. No i jak się uprzesz tykać konstrukcji nienaturalnych dla Javy to czeka cię coś w stylu : "`ActorSystem$`.`MODULE$`.apply()". Można to "owrapować" kotlinowym API ale ogólnie - aktor jest stworzony przez Scalowe API a ActorSystem przez Javowe i wszystko działa elegancko.

Wnioski

Hejty na bok ale jak twórcy Akki dodali Javowe API by przyśpieszyć adaptację tej biblioteki w korpo to moim zdaniem dobrze mieć Kotlina gdzieś na radarze bo to się czuje, że Kotlin celuje w bycie lepszą Javą i to się może udać. Część druga wkrótce a cześć ósma jak będzie 100 000 lajków.

niedziela, 21 stycznia 2018

Java9 Cleaner świetną ilustracją enkapsulacji

10 lat temu w Effective java mogliśmy przeczytać by nigdy nie używać finalize. Ja się zastosowałem i tylko raz niechcący w ferworze refaktoringu przezwałem jakąś metode na finalize i zaczęły dziać się śmieszne rzeczy. Latka minęły i świeżo na półeczki księgarni informatyka wyszło trzecie wydanie Effective java z apdejtem do Java 9. No i w tej najnowszej Javie (najnowszej jeszcze tylko 2 miesiące!) w rozdziale o finalize pojawia się nowy mechanizm java.lang.ref.Cleaner

Koniec końców Joshua Bloch napisał, że ten Cleaner tez zjebany ale ogólniej mniej i jest taki jeden kejs gdzie go bez strachu użyć można. No i tak się składa, że moim zdaniem ten przykład użycia jednocześnie niesie ze sobą ogromną wartość edukacyjną jak właściwie enkapsulować stan w klasie. A do tego możemy sobie zobaczyć jak inne języki na JVM poradzą sobie ze standardową sytuacją w Javie.

Przez chwile miałem wątpliwości czy aby temat będzie dla ogółu interesujący ale potem sobie przypomniałem, że to mój blog i będę tutaj pisał co mi się chce.

Dobra Enkapsulacja

Na poczatku wytniemy kawałeczki kodu by łatwiej było zauważyć wspomnianą enkapsulację i ogólnie pojęte ukrywanie informacji, które bardzo pomaga w utrzymaniu i rozbudowie systemu. Jeśli ktoś chce brzmieć bardziej Fancy to może używać terminu anglojęzycznego https://en.wikipedia.org/wiki/Information_hiding. Jeśli ktoś chce brzmieć bardziej biznesowo to może powiedzieć, że informejszyn hajding redukuje ołweral meintenance cost and reduces tajm to market for fiuczer rilises.

public class InstanceAroundResource implements AutoCloseable{
    //We will delegeta cleaning to Cleaner from Java 9
    private static final Cleaner cleaner=Cleaner.create();

    //This is definition of internal state, static -> so it has no ref to external instance
    //private - to better hide information
    private static class EncapsulatedResource implements Runnable{
       (...)
    }

    //no getters for those two fields, no strange hakiers annotations either
    private final EncapsulatedResource state;

    //this triggers cleaning procedure
    private final Cleaner.Cleanable cleanable;

    public InstanceAroundResource(String resourceId) {
        //notice that both instances are created inside constructor , no direct assignment, 
        //no information how resourceId is escapes outside 
        //compare this with stntaxt 'this.field = field' which is hiding information like in the sentence 
        // "think about number seven but don't tell what the number is"
        this.state = new EncapsulatedResource("[opened :"+resourceId+"]");
        this.cleanable = cleaner.register(this, state);
    }

    // here we are connecting Cleaner with Autocloseable from Java7
    @Override
    public void close() throws Exception {
        cleanable.clean();
    }
}

I teraz tak, po pierwsze primo od javy 9 mamy import do dyspozycji:

import java.lang.ref.Cleaner;

i idąc dalej :
  1. Zauważcie, że nic z tej klasy nie ucieka poza abstrakcyjnem resourceId przekazanym do konstruktora - to jest niejako kawałek publicznego interfejsu.
  2. Drugim kawałkiem publicznego interfejsu jest - no własnie interfejs - Autocloseable , czyli obietnicę posiadania metody/zachowania close
  3. Totalnie ukryliśmy przed użytkownikiem użycie Cleanera. Nie ma ani dziwnych ch** wie po co getterów setterów.
  4. Jak Bloch przykazał pola są finalne - przynajmniej pod katem referencji wnętrze jest niezmienne.
  5. Ukryliśmy przed światem zewnętrznym istnienie klasy EncapsulatedResource co daje nam pełne pole do refaktoringu tego kawałka kodu w przyszłości (i minimalizacji tajm to market)
  6. Jeszcze raz podkreslmy, że parametry konstruktora również nie zdradzają za wiele o bebechach.
  7. Sam Cleaner z tego co zrozumiałem jest lepszy od starej metody finalize pod tym katem, że delegując czyszczenie do Cleanera - który nasłuchuje (listenerek taki) - nie będzie problemu z wyjątkami przy sprzątaniu bo Cleaner ma pełną kontrolę nad tym watkiem/procedurą i ogarnia. Tak przynajmniej napisał autor i ja mu wierzę!

I cały kod

import java.lang.ref.Cleaner;

public class InstanceAroundResource implements AutoCloseable{
    private static final Cleaner cleaner=Cleaner.create();

    //static to not have reference to external instance
    private static class EncapsulatedResource implements Runnable{

        String handleToSystemResource; //don't need to be private because EncapsulatedResource is private

        EncapsulatedResource(String handleTosystemResource) {
            this.handleToSystemResource = handleTosystemResource;
        }

        @Override
        public void run() {
            System.out.println("Closing system resource by cleaner :"+handleToSystemResource);
            handleToSystemResource="CLOSED";
        }
    }

    private final EncapsulatedResource state;

    private final Cleaner.Cleanable cleanable;

    public InstanceAroundResource(String resourceId) {
        this.state = new EncapsulatedResource("[opened :"+resourceId+"]");
        this.cleanable = cleaner.register(this, state);
    }

    @Override
    public void close() throws Exception {
        System.out.println("First In Auto-Closable");
        cleanable.clean();
    }
}

Zerknijmy teraz na odpalenie tego :

        //1
        try(InstanceAroundResource r = new InstanceAroundResource("BLOG-POST")){
            System.out.println("Using resource1");
        }

        //2
        new InstanceAroundResource("UNHANDLED-RESOURCE");
        System.out.println("r2 left alone");
        //System.gc();

sytuacja nr 1 prosta bo na końcu zostanie wykonane close i mamy

Using resource1
First In Auto-Closable
Closing system resource by cleaner :[opened :BLOG-POST]
Co do drugiej sytuacji to podobnie jak u autora książki także i u mnie backup nie zadziałał bez sztucznego wywołania System.gc()
r2 left alone
Closing system resource by cleaner :[opened :UNHANDLED-RESOURCE]

A Java 10 ?

W Javie 10 zadziała już poniższe :

jshell> try(var r=new InstanceAroundResource("Java10")){
   ...> System.out.println("Doing something in java 10");
   ...> }
Doing something in java 10
First In Auto-Closable
Closing system resource by cleaner :[opened :Java10]

Czyli już nie trzeba deklarować nowego InstanceAroundResource z zaznaczeniem iż jest typu InstanceAroundResource. Mniej czytania, inżynier może przeznaczyć moc operacyjną muzgu na redukcję tajm to market.

Inne Języki

Kotlin

Kotlin ma coś takiego :

public inline fun <T : AutoCloseable?, R> T.use(block: (T) -> R): R {
    var exception: Throwable? = null
    try {
        return block(this)
    } catch (e: Throwable) {
        exception = e
        throw e
    } finally {
        this.closeFinally(exception)
    }
}

Co oznacza, że mechanizmem "extends method" dorzuci metodę use do wszystkiego co rozszerza AutoCloseable. Także zadziała to i z naszym - napisanym w Javie - InstanceAroundResource.

val r=InstanceAroundResource("KOTLIN-EXAMPLE")

r.use { r: InstanceAroundResource ->
        println("Doing something in kotlin")
}

I to w sumie w niektórych kręgach półświatka programistycznego można nazwać metodą/funkcją wyższego rzędu. Wynik będzie analogiczny z przykładem Javowym :
Using resource1
First In Auto-Closable
Closing system resource by cleaner :[opened :BLOG-POST]

No i analogie idzie dalej, jeśli chcemy by cleaner zadziałał jako backup trzeba ręcznie gc wywołać :

InstanceAroundResource("UNHANDLED-IN-KOTLIN")
println("r2 left alone")
System.gc()

A co z nullem

Ogarnianie nulli z javy to jeden z lepszych fiuczerów Kotlina, zerknijmy czy jebnie coś takiego :

val r3:InstanceAroundResource? = null
r3.use { r: InstanceAroundResource? -> println("jebnie?") } //nie jebnie

No i po pierwsze nie da się łatwo oszukać kompilatora, że null jest typu InstanceAroundResource (ten taki znak zapytania na końcu) a po drugie to nie jebnie. Jak zerkniesz jeszcze raz na implementacje "use" to zobaczysz , ze w finally nie ma "close" a jest " this.closeFinally(exception)". I to wygląda mniej więcej tak :
internal fun AutoCloseable?.closeFinally(cause: Throwable?) = when {
    this == null -> {}
    cause == null -> close()
    else ->
        try {
            close()
        } catch (closeException: Throwable) {
            cause.addSuppressed(closeException)
        }
}

Czyli takie zamknięcie-otwarcie RISORSA nie jest rzeczą prostą. I całe szczęście, że Java i Kotlin mają gotowce na to w standardowej bibliotece. I tutaj też zwróć uwagę, że Java natywnie na poziomie języka a kotlin zaimplementowane przy pomocy bardziej abstrakcyjnego natywnego mechanizmu - "rozszerzalnych metod". Zaraz zobaczymy, ze to może iść jeszcze dalej.

Scala

I teraz tak : wiem, że google jakoś segreguje wyniki wyszukiwania na podstawie ciasteczek i mogą być dla każdego unikalne ale jak wpisałem w googla własnie "scala try with resources " to kurcze same tutoriale wyskakują jak samemu napisać a nie byłem w stanie jakiejś natywnej konstrukcji odnaleźć.

jak ktoś zna to niech da namiar bo inaczej ryzykujemy w każdym projeckie "not inwented here" z pytaniem "a w której paczce mamy zaimplementowany loan-pattern, no wiesz ten try z javy?". Jest biblioteczka https://github.com/jsuereth/scala-arm ale tez jakos nie jest dobrze na googlu spozycjonowana a znalazłem ją bo już o niej kiedyś słyszałem. W każdym razie jak już biblioteczkę mamy to dalej kod wygląda bardzo podobnie:

val r=new InstanceAroundResource("SCALA-EXAMPLE")

val mr: ManagedResource[InstanceAroundResource] = managed(r)

mr.foreach{r=>
        println("Doing something in scala")
}

new InstanceAroundResource("UNHANDLED-IN-SCALA")
println("r2 left alone")
System.gc()
Doing something in scala
First In Auto-Closable
Closing system resource by cleaner :[opened :SCALA-EXAMPLE]
r2 left alone
Closing system resource by cleaner :[opened :UNHANDLED-IN-SCALA]

Poziomy abstrakcji mechanizmów językowych

Obczajcie to :

  • Java ma try-with-resources
  • Kotlin ma extension method przy pomocy, którego można zaimplementować try-with-resources i inne rozszerzenia
  • Scala ma implicity przy pomocy których można zaimplementować extension method i inne meta-rozszerzenia przy pomocy których można zaimplementować try-with-resources i inne rozszerzenia
No tylko, że im bardziej ogólny mechanizm tym trudniej go komuś wytłumaczyć i dłuższa edukacja - try-with-resources jest banalne w użyciu a teraz porównaj to z ogarnianiem implicit class i jakichś optymalizacji jak rozszerzanie AnyVal. Także coś za coś

Linki