czwartek, 18 sierpnia 2016

Historia Kulki Animowanej

Jest to wpis o historii pewnej Kulki. Nie jest to jednak zwykła kulka bo to historia kulki animowanej i to animowanej javascriptem. I nawet ona spada według jakichś tam praw fizyki i nawet za sobą zostawia ślad (rysowany też pośrednio javascriptem). Jest to kulka w javascripcie ale bez pisania w javascripcie.

Podążając za poprzednim dającym (na opewno mnie) nadzieję na łeba wpisem "Scala.js taka piękna" cały javascript jest wygenerowany przy pomocy Scala.js. I (jak dla mnie) kod jest fajnie. Bo są typy. I Jest fajnie.

Kino 8D

Czcionka na gifach jest mała dlatego trzeba w nie kliknąć albo skrolem powiększyć. Na pierwszym przeźroczu widać, że wielkość kulki można konfigurować i ona tak rośnie i maleje wtedy od razu. Do tego na bieżąco liczona jest masa tejże kulki z założeniem, że gęstość to średnia gęstość meteorytu czyli 3.4 g/cm^3

Jest tam także validacja pola gdzie można wprowadzić promień kulki. A, że rrrr to nie jest najlepsza wartość dla promienia dlatego warto ją sprawdzić. Tutaj dla podkreślenia możliwości Scali.js do validacji została użyta Monada. Jest to tylko Try ale zawsze Monoada®© Zawsze można się (nie)znajomością teorii kategorii popisać przed kolegami od front-endu.

No i w końcu "leć Adam leć". Zwróćcie uwagę, że wraz z czasem odległość pomiędzy czerwonymi kropkami się zwiększa a to dlatego, że działa przyspieszenie i składowa y prędkość się także powiększa z czasem co obrazuje wydłużająca się pionowa strzałka symbolizująca pionowa składową wektora prędkości.Dokładnie tak. W symulacji użyte jest przyśpieszenie księżyca 1.6 m/s aby kulka za szybko nie spadła i aby spektakl się za szybko nie zakończył.

"Z życia programisty front-endu"

Cały eksperyment miał za zadanie sprawdzenie "Jak się w tym pisze". Pierwsza ważna rzecz na jaką należy zwrócić uwagę przy zasiadaniu do nowej technologii to czy na StackOverflow są już odpowiedzi do wszystkich zadań

Generalnie te strzałki w symulacji moje nie są jeśli za "moje" uznać napisane własnoręcznie. Po wpisaniu w google arrow canvas javascript mamy ten link :

I na przykład tak to wygląda w javascript (kim czym - jawaskrypcie?)

//Javascript
function canvas_arrow(context, fromx, fromy, tox, toy){
    var headlen = 10;   // length of head in pixels
    var angle = Math.atan2(toy-fromy,tox-fromx);
    context.moveTo(fromx, fromy);
    context.lineTo(tox, toy);
    context.lineTo(tox-headlen*Math.cos(angle-Math.PI/6),
      toy-headlen*Math.sin(angle-Math.PI/6));
    context.moveTo(tox, toy);
    context.lineTo(tox-headlen*Math.cos(angle+Math.PI/6),
     toy-headlen*Math.sin(angle+Math.PI/6));
}

i naprawdę niewiele trzeba by ten kod zadziałał w Scala.js

// Scala.js
def canvas_arrow(fromx:Int, fromy:Int, tox:Int, toy:Int){
      val headlen = 10   // length of head in pixels
      val angle = Math.atan2(toy-fromy,tox-fromx)
      ctx.moveTo(fromx, fromy)
      ctx.lineTo(tox, toy)
      ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/6),
       toy-headlen*Math.sin(angle-Math.PI/6))
      ctx.moveTo(tox, toy)
      ctx.lineTo(tox-headlen*Math.cos(angle+Math.PI/6),
        toy-headlen*Math.sin(angle+Math.PI/6))
    }

Jestem w DOMu

Generalnie jedyna niedogodność do jakiej można się doczepić, to że używając "zwykłego DOM" pobieramy generyczne elementy HTML, które trzeba mapowac na konkretne Typy

//Scala.js
//jest troche więcej rzutowania przy pobieraniu elementów
val canvas=document.getElementById("canvas").asInstanceOf[Canvas]
val context=canvas.getContext("2d").asInstanceOf[dom.CanvasRenderingContext2D]

//ale dalej jest już w zasadzie to samo
context.fillStyle="#FF0000"
context.fillRect(0,0,150,75)

Ktoś przyzwyczajony do Javascript może narzekać, że to jakiś dodatkowy wysiłek ale to jest inwestycja, która zwraca się w dalszej części pracy z tymi elementami kiedy zabezpieczają nas te właśnie typy typy. (No chyba, że ktoś źle zrzutuje wtedy boli)

//Javascript 

var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.fillStyle = "#FF0000";
ctx.fillRect(0,0,150,75);

Generalnie można pisać w Scala.js korzystając z dokumentacji do javascript. Ot choćby mechanizmy z tej stronki działają w Scali jak najbardziej : http://www.w3schools.com/html/html5_canvas.asp

Troski o jednostki

Na razie Scala.js goni javascript ale czy może zaoferować coś czego tam nie ma? (ewentualnie jest ale ja nie wiem, że jest). Otóż jak już ogarniemy przyciski i inputy to jest pewien problem poniżej bo trzeba nam np. wyliczyć masę ze wzoru m=ρ * (4/3) * π * r3 gdzie ρ - to gęstość. I teraz weź to wszystko licz przy pomocy double gdy gęstość to g/cm3 a promień jest w metrach. Czy też licz drogę w kilometrach gdy prędkość metr/sekunda a czas symulacji jest w milisekundach by się to jakoś odświeżało.

I w scali np moge stworzyć sobie odpowiednie typy, które będą kontrolowały jednostki. To poniżej nie jest jakimś szczytem modelowania domenowego bo Centy,Mili,Kilo trzeba powtarzać dla każdej jednostki ale generalnie chodzi o eksperyment jak to się sprawdzi w Scala.js (No i dla porównania zmienię formatowanie kodu bo nie mogę się zdecydować czy lepsze jest jaśniejsze czy ciemniejsze.)

object Distance{

  case class Centimeter(v:Double) extends AnyVal {
    def meters = Meter(v/100)
  }

  case class Meter(v:Double) extends AnyVal{
    def centimeters=Centimeter(v*100.0)
  }
  case class KiloMeter(v:Double) extends AnyVal{
    def meters=Meter(v*1000.0)
  }

  implicit def meterToDouble(m:Meter):Double=m.v
}

Pierwsza rzecz to kontrola przy liczeniu objętości kuli. Chcemy utrzymać czyste jednostki bez żadnego przedrostka.

case class Volume(v:Double) extends AnyVal

def sphereVolume(r:Meter):Volume=Volume(4.0/3.0 * PI * pow(r,3.0))

No i drogi czytelniku/czytelniczko mam nadzieję, że zauważyłeś/aś tam ciekawą rzecz, że podajemy bezpośrednio promień który przecież jest typu "Meter" do Math.pow. A to dlatego, że zaimportowaliśmy konwersję z modułu Distance

implicit def meterToDouble(m:Meter):Double=m.v

No i jeszcze jedna rzecz - AnyVal. W Scali kompilator rozpakuje prymitywa i w runtime nie ma żadnego narzutu a w trakcie kompilacji typy zabezpieczają. (to tak jak wypić wódkę i mieć wódkę czy coś takiego)

No i z tego kodu już po kompilacji do js wynika, że to też działa bo bez AnyVal jest wersja gdzie wartość z promienia trzeba dopiero wydobyć (czyli to tak jak mieć ciastko i zjeść ciastko albo tak jak nie płacić abonamentu a i tak oglądać telewizję).

//Z valueclass
$c_Lcom_wlodar_physics_Geometry$.prototype.sphereVolume__D__D = (function(r) {
  return (4.1887902047863905 * $uD($g.Math.pow(r, 3.0)))
});

//bez value class
$c_Lcom_wlodar_physics_Geometry$.prototype.sphereVolume__Lcom_wlodar_physics_Distance$Meter__D = (function(r) {
  var a = r.v$1;
  return (4.1887902047863905 * $uD($g.Math.pow(a, 3.0)))
});

Rzeczy nowe

Przy okazji takiego programu pojawia się kilka ciekawych wyzwań, których w zwykłych CRUDach nie ma. No bo tam jest taki stan, który wydaje sie wygodny czyli np. aktualne położenie kulki i jakoś monady mnie się tu nie widzą. Można by zawczasu "obliczyć" sumylację i później ją odpalić wtedy niejako kolejne klatki byłyby kolejną wersją niemutowalnego stanu na stosie czy czymś takim i w sumie tak historia ruchu (te czerwone kropki) jest wyliczana.

Ale z drugiej strony na courserze przy okazji zeszłorocznego kursu o Reactive Application było coś o sygnałach i "Functional Reactive Programming". Także też ciekawe.

Testowanie

Tę część z jednostkami to nawet i zwykłym ScalaTestem da się pojechać bo to zwykła taka Scala-Scala. Można Scalachecka użyć do Property Based Testing no i są specjalne narzędzia które testy tłumacza na javascript i odpalają je już na takim "RuntimeDom"

Kulka

Jest pod adresem :

adres kulki.

Niestety jak wstawiłem iframe to blogger mi powiedział, że nie ma tego ficzeru. Generalnie chyba najfajniej wygląda jak wpisze się 100 w promień i 150 w prędkość poziomą. Mozna tez sobie potestować podawanie złych wartości - Try daje radę przy podawaniu liter zamiast cyfr a dalej już nie testowałem.

Jak na kogoś kto ostatni raz javascripta robił w 2013 i teraz taka kulka... no czuję się taki szcześliwy...

czwartek, 4 sierpnia 2016

I nawet dla ciebie jest nadzieja w webie

Już na samym początku - aby uniknąć niepotrzebnych dyskusji o tym, który młotek jest najlepszy - przywołamy magiczne słowo "warunkowe". Z matematki taki zapis : P(B|A)=... , określa, że będziemy liczyć wystąpienie prawdopodobieństwa B pod warunkiem, że zaszło zdarzenie A. I dalej idąc w bardzo luźną analogie rozpatrzymy dzisiaj sytuację : U(scala.js|scala) czyli

U - szansa, że uda ci się coś zrobić w scala.js jeśli już robisz w scali 
i nie masz czasu co trzy godziny uczyć się nowego frameworka javascriptowego.

Czas i (mała) przestrzeń

Czasem w niektórych firmach istnieje niewidzialny klej, który przytwierdza ludzi do jednego projektu na całe ich życie (a w kontrakcie jest zapisane, że po śmierci ich prace będą kontynuować ich dzieci bo nikt inny nie skuma tego kodu). Często poletko, którym się zajmują ma 1 metr kwadratowy i pomija wiele ciekawych aspektów programowania. I tak jak ktoś bijący się z przeglądarką może znać wątki ale tylko te z mody na sukces. A ktoś działający na back-endzie widzi javascript jako doskonałe narzędzie do wyświetlania alertów.

Ponieważ sam ostatnie półtora roku miałem takie, że główny kontakt z webem był gdy dokumentacje do sparka otwierałem w przeglądarce także jeśli istnieje szansa na robienie weba przy pomocy ekosystemu, który jako tako znam to zamiast ogarniać całą osobną gałąź przemysłu od początku (jakieś grunty srunty) to takiej możliwości warto się przyjrzeć.

To nie jest tutorial bo tutorial już jest : http://www.lihaoyi.com/hands-on-scala-js/. Chodzi bardziej o wrażenia. Wrażenia i odczucia a może nawet i uczucia. To nieprawda, że programiści nie mają uczuć. ja np. często czuję, że coś nie zadziała.

robić Łeba

Bardzo dobry tutorial na wstęp jest tutaj : https://www.scala-js.org/tutorial/basic/ i jak to tutorial - działa dopóki robi się wszystko według tutoriala. Ale warto trochę zbaczać i psuć bo wtedy można zrozumieć to co się robi w szerszym kontekście.

Ten tutorial tworzy prostą stronkę z javascriptem. Może i warto na początek za dużo nie kombinować i go po prostu zrobić ale dalszą ciekawsza wariacją będzie próba takiego para-produkcyjnego użycia scala.js w aplikacji webowej. Do tworzenia wara użyjemy scalatry. Scalatra jest bardzo przyjemnym narzędziem bo już chyba ku*wa prostszego frameworku do tworzenia aplikacji webowej wymyślić nie można.

Dodatkowy zysk edukacyjny pracy na scalatrze jest taki, że łatwiej zrozumieć jak współdziałają te wszystkie pluginy SBT. W Playu wszystko jest takie od razu scalone - "which is good" - ale można przez tę fasadę nie dojrzeć wielu szczegółów.

Scala.js ma swój plugin, który kompiluje do "targetu" a Scalatra siedzi na XSBT-WEB, który zbiera javascript z katalogu "src/main/webapp". Jest kilka szablonów na necie, które pokazują jak zgrać prawidłowo te dwa narzędzia (na przykład : ) zazwyczaj w postaci dwóch niezależnych modułów.

Na potrzeby bloga wybiorę prostsze podejście czyli kopiowanie plików na pałę.

val kopiujNaPałęJavascript: TaskKey[String] =taskKey[String]("compileJavascript")

kopiujNaPałęJavascript := {
  val jsHome = sourceDirectory.value / "main" / "webapp" / "js"
  val dependenciesFileName = "scalatra-js-jsdeps.js"

  val generatedFile: File =(fastOptJS in Compile).value.data
  val generatedDepFile=new File(generatedFile.getParent,dependenciesFileName)

  val destinationFile = jsHome / generatedFile.getName
  val dependenciesFile = jsHome / dependenciesFileName

  IO.copyFile(generatedFile,destinationFile)
  IO.copyFile(generatedDepFile,dependenciesFile)

  jsHome.getAbsolutePath
}

"scalatra-js-jsdeps.js" to plik z wszyskimi zależnościami takimi jak jquery. A no bo właśnie - można sobie zainstalować fasadę na jquery i mieć takie "w-pół" statyczne typowanie w tym jakże wygodnym frameworku.

jQuery("#demoButton").click(processInput _)

def processInput():Unit={
....   
}

No i to też się do testowania przyda później. Apropo JQuery to jeszcze taka dygresja

Dygresja o rzeczach niefizycznych

W arcy ciekawej książce Antifragile Nicholas Taleb opisuje koncepcję żywotności rzeczy niematerialnych, która wybiega trochę na przeciw naszej intuicji. Dzieje się tak gdyż w przypadku rzeczy materialnych im dłużej dana rzecz istnieje tym prawdopodobnie będzie istnieć krócej. I tak np. mamy 1 letni i 10 letni samochód tej samej marki - intuicja nam podpowiada, że - wyłączając zdarzenia losowe - ten starszy szybciej pójdzie na złom.

Teraz weźmy rzecz niematerialną jak choćby język programowania. Mamy dwa języki - jeden wydany rok temu a drugi przed 10ciu laty. Pytanie, który wcześniej się rozpłynie w nicości. No i tutaj może być trudniej to ogarnąć bo generalnie język jako koncept nie rdzewieje, nie wpieprzają go korniki ani nic takiego. żywotność konceptu jest zależna tylko od nośnika i jeśli jakieś nośniki od 10 lat przenoszą ten koncept to oznacza, że z jakiegoś powodu wygrał w ruletce "selekcji naturalnej" języków programowania. A ten co ma rok czy nawet miesiąc - nie wiadomo.

No i mamy takie JQuery, które już ma 10 latek i przetrwało selekcje a 10 lat temu był w użyciu też inny wynalazek - prototype.js. Na przełomie 2006/2007 był jednym z niewielu szczepionek na popularną w webdeveloperce chorobę zwaną IE6 (gimby nie znajo i ich kurwa szczęście...). A teraz według wiki używa go 2,2% stron czyli pewnie twórca, jego sąsiad i kilka korpo z miód-maintenecem. Na wiki możecie sobie poczytać co poszło nie tak.

Także jak z dnia na dzień zobaczysz jakiś super nowy hiper frejmowrk weź wdech, dokończ robote i w wolnej chwili na spokojnie się temu przyjrzysz.

Moje pole

Celem laboratorium będzie walidacja jednego pola po stronie klienta, wysłanie go na serwer , ponowna validacja i odesłanie jakiejś odpowiedzi.

Nie ma sensu wklejać całego kodu bo to luźno zwisający HTML i kilka instrukcji scali także skoncentrujemy się na najważniejszych fragmentach. No i tak na poniższym wycinku rejestrujemy "onklika" , po którym zgarniemy wartość z pola i przy pomocy scalowego (a jakże) pattern matchingu sparsujemy wartość i podążymy jedną z dwóch ścieżek : błędu lub szczęścia.

jQuery("#demoButton").click(processInput _)

def processInput():Unit={
    jQuery("#wynik").remove()
    val mojePole: Dynamic =jQuery("#mojePole").value()

    Try(mojePole.toString.trim.toInt) match {
      case Success(v) => validInput(v)
      case Failure(e) => wrongInput(e)
    }
}

Jak sie popsuło to będzie na czerwono :

 def wrongInput(e: Throwable)= {
    jQuery("body").prepend(s"""
         |<div class="alert alert-danger" role="alert" id="wynik">ERROR : ${e}</div>
         """.stripMargin)
  }

Do serwera

I oto dwa kawałki kodu napisane w jednym i tym samym silnie typowanym języku siedzące w dwóch różnych miejscach internetu (w teorii bo w praktyce w tym przykładzie oba są na moim kompie) komunikują się ze sobą!

//scala.js - browser
Ajax.post(s"/validate?value=$v").onComplete{
      case Success(xhr) => prependResponse(xhr.responseText)
      case Failure(e) => prependResponse(s"zjebało się : $e")
}


//scalatra-serwer
post("/validate"){
    Try(params("value")).map(v=>
      Ok(s"z serwera $v")
    ) getOrElse halt(BadRequest())  //400
}

Scalatra jak mówiłem prosta i przyjemna także taki prosty kodzik obsługuje post. No i wynik :

I działa

Błędy w runtime

W runtime działa już sobie javascript, który w teorii powinien być dobry jeśli tylko został wygenerowany z kompilującej się scali (plus pewnie bliżej nieokreślone N nieprzewidzianych bugów). Natomiast nic się nie dzieje gdy wyszukuję nieistniejący element na stronie i coś tam działam - w sensie nie dzieje się na chromie bo w testach ładnie się wywala. A testy pisze się np. w tym : https://github.com/lihaoyi/utest czyli narzędzia są.

Wygląda obiecująco

czwartek, 28 lipca 2016

Scala 2.12 - laborki

Scala 2.12 ma wyjść jakoś już zaraz niedługo niebawem i jak tu nie lepiej posprawdzać co nas czeka jak nie pokompilować sobie próbek kodu. Tak jak na laborkach w technikum. Tyle, że tam sprawozdanie było od razu gotowe, wnioski spisane i w zasadzie mierzyło się do czasu aż wykres namalował się zgodnie z oczekiwaniem. A tak to w zasadzie podobnie.

Sprawdzimy faktycznie co się dzieje kompilując sobie scalę 2.12-M5 , którą to wersję można sobie ściągnąć tutaj --> http://www.scala-lang.org/download/2.12.0-M5.html oraz javap i innych takich narzędzi do roz-kompilowywania kodu.

Świat po kompilacji - Prosta Klasa z Funkcją

Według zapowiedzi kod ma być kompilowany do BAJTkodu javy 8 czyli funkcje już nie powinny zamieniać się w klasy anonimowe.

Mamy taki prosty kod w Scali

class Lib{
  val fun:Int=>Int=_+1
}

Scala 2.11

Po kompilacji mamy takie oto wygenerowane pliki.Uwagę zapewne przyciąga to takie długie coś - o tym za chwilkę

ls
Lib$$anonfun$1.class  Lib.class  lib.scala

Najpierw : Lib.class - tutaj w sumie nic nadzwyczajnego.
javap -private  Lib.class 
Compiled from "lib.scala"
public class Lib {
  private final scala.Function1<java.lang.Object, java.lang.Object> fun;
  public scala.Function1<java.lang.Object, java.lang.Object> fun();
  public Lib();
}



Scala 2.11 kompiluje się do bytecodu Javy 6 a jako, że w Javie 6 lambd nie ma to mamy dodatkowa tę oto wcześniej wspomnianą dziwną klasę

Lib$$anonfun$1.class
javap Lib\$\$anonfun\$1.class 
Compiled from "lib.scala"
public final class Lib$$anonfun$1 extends scala.runtime.AbstractFunction1$mcII$sp implements scala.Serializable {
  public static final long serialVersionUID;
  public final int apply(int);
  public int apply$mcII$sp(int);
  public final java.lang.Object apply(java.lang.Object);
  public Lib$$anonfun$1(Lib);
}

Przykład jest prosty i dostaliśmy tylko jedną anonimową klasę ale jak polecimy z nim trochę dalej i trochę mocniej

class Lib{
  val fun:Int=>Int=_+1
  val fun2:Int=>Int=_+1
  val fun3:Int=>Int=_+1
  val fun4:Int=>Int=_+1
  val fun5:String => BigDecimal = s => BigDecimal(s)
}

To generuje się tego wincyj i wincyj

ls
Lib$$anonfun$1.class  Lib$$anonfun$2.class  Lib$$anonfun$3.class  
Lib$$anonfun$4.class  Lib$$anonfun$5.class  Lib.class  lib.scala

A klasa bohaterka wygląda ostatecznie tak :

public class Lib {
  private final scala.Function1<java.lang.Object, java.lang.Object> fun;
  private final scala.Function1<java.lang.Object, java.lang.Object> fun2;
  private final scala.Function1<java.lang.Object, java.lang.Object> fun3;
  private final scala.Function1<java.lang.Object, java.lang.Object> fun4;
  private final scala.Function1<java.lang.String, scala.math.BigDecimal> fun5;
  public scala.Function1<java.lang.Object, java.lang.Object> fun();
  public scala.Function1<java.lang.Object, java.lang.Object> fun2();
  public scala.Function1<java.lang.Object, java.lang.Object> fun3();
  public scala.Function1<java.lang.Object, java.lang.Object> fun4();
  public scala.Function1<java.lang.String, scala.math.BigDecimal> fun5();
  public Lib();
}

No dobra ale to już pomału odchodzi w przeszłość bo oto (pamparampampam):

Scala 2.12

Klas anonimowych w 2.12 nima :

ls
Lib.class  lib.scala

Klasa z funkcjami wygląda podobnie do 2.11

public class Lib {
  public scala.Function1<java.lang.Object, java.lang.Object> fun();
  public scala.Function1<java.lang.Object, java.lang.Object> fun2();
  public scala.Function1<java.lang.Object, java.lang.Object> fun3();
  public scala.Function1<java.lang.Object, java.lang.Object> fun4();
  public scala.Function1<java.lang.String, scala.math.BigDecimal> fun5();
  public static final int $anonfun$fun$1(int);
  public static final int $anonfun$fun2$1(int);
  public static final int $anonfun$fun3$1(int);
  public static final int $anonfun$fun4$1(int);
  public static final scala.math.BigDecimal $anonfun$fun5$1(java.lang.String);
  public Lib();
}

I to wszystko! Jak to możliwe zapytacie!?! Ano

javap -c Lib.class

public Lib();
    Code:
       0: aload_0
       1: invokespecial #67                 // Method java/lang/Object."<init>":()V
       4: aload_0
       5: invokedynamic #87,  0             // InvokeDynamic #0:apply$mcII$sp:()Lscala/runtime/java8/JFunction1$mcII$sp;
      10: putfield      #24                 // Field fun:Lscala/Function1;
      13: aload_0
      14: invokedynamic #91,  0             // InvokeDynamic #1:apply$mcII$sp:()Lscala/runtime/java8/JFunction1$mcII$sp;
      19: putfield      #28                 // Field fun2:Lscala/Function1;
      22: aload_0
      23: invokedynamic #95,  0             // InvokeDynamic #2:apply$mcII$sp:()Lscala/runtime/java8/JFunction1$mcII$sp;
      28: putfield      #30                 // Field fun3:Lscala/Function1;

Ten byte kod należy czytać "costam costam InvokeDynamic costam costam" czyli oto lambdy z java 8 właśnie widzimy. No czyli jest ładnie i działa i nie ma sensu się dalej rozpisywać bo jest ładnie i działa.

Traity i interfejsy

Od Javy 8 interfejsy mogą mieć metody statyczne oraz domyślne implementacje zwykłych metod. W ciekawym artykule o Scali 2.12 http://www.scala-lang.org/blog/2016/07/08/trait-method-performance.html możemy wyczytać, że :

"2.12.0-M5: trait method bodies are emitted in static methods in the interface classfile. The default methods forward to the static methods."

Kod wyjściowy w Scali

trait A{
  def m()
}

trait B{
  def m()

  def m2(arg:Int) :Int = arg+20
}

trait C{

  val field=15

  def m()
  def m2(arg:Int) :Int = arg+field
}

Scala 2.11

Poza traitem A, który ma tylko deklaracje metody - resztę traitów nie da się przedstawić przy pomocy zwykłego interfejsu z Javy6 dlatego też generowane są dodatkowe klasy z implementacjami metod

ls
A.class  B.class  B$class.class  C.class  C$class.class  lib.scala

Scala 2.12

Tutaj wygenerowanych plików jest mniej właśnie dzięki nowym patentom w interfejsie Javy8

ls
A.class  B.class  C.class  lib.scala

I nawet to C z definicją wartości w polu traitu da się przedstawić jako interfejs w Javie8

javap  C.class 
Compiled from "lib.scala"
public interface C {
  public abstract void C$_setter_$field_$eq(int);
  public abstract int field();
  public abstract void m();
  public static int m2$(C, int);
  public int m2(int);
  public static void $init$(C);
}


Wołanie javy 8

Scala Lambdy Jako Java Lambdy

wywołajmy sobie Streamy z Javy8

object Main extends App{
java.util.Arrays.asList(1,2,3,4,5).stream().map(e=>e+1).forEach(i=>println(s"INFO : $i"))
}


I wynik

scala2.12 lib.scala 
INFO : 2
INFO : 3
INFO : 4
INFO : 5
INFO : 6



Czyli poooszło ładnie!

Funkcje Scali zamiast Lambd

object Main extends App{
  val increment:Int=>Int=e=>e+1
  val display: Int=>Unit = i=>println(s"INFO : $i")

  java.util.Arrays.asList(1,2,3,4,5).stream().map(increment).forEach(display)
}



To niestety nie zadziałało. Być może trzeba jakieś konwersje zaimportować.

scala2.12 lib.scala 
/home/pawel/projects/scalaeksperymenty/212/212/lib.scala:14: error: type mismatch;
 found   : Int => Int
 required: java.util.function.Function[_ >: Int, _]
  java.util.Arrays.asList(1,2,3,4,5).stream().map(increment).forEach(display)
                                                  ^
one error found


Także ogólnie jeśli nie będzie żadnego globalnego kataklizmu to przyszłość programowania zapowiada się wspaniale.

środa, 6 lipca 2016

Einstellung Effect

"The difficulty lies, not in the new ideas, but in escaping from the old ones, which ramify ... into every corner of our minds."

To był taki krótki, fajny i wymowny cytat na wstęp a teraz jeszcze jeden dłuższy ale równie fajny i przepełniony niebagatelną informacją :

The Einstellung Effect refers to the human tendency to repeat a known solution, even if it no longer is the optimium solution. It represents the negative value of experience. The Einstellung Effect kicks in when new solutions have developed or become available but we do not look for them or adopt them even if we know of them. This can happen because we believe we already know the best solution, hence do not fully evaluate the new ways. Or simply because we are set in our ways and do not wish to undertake the “pain” of learning new ways unless forced to by others or market events.

Jeszcze metaforyczny rysunek i można zaczynać.

"Don't be blind,"

Na wzmianka o tym efekcie natrafiłem w fajnym kursie o tym "jak się uczyć" - Learning How to Learn, który jest w tej chwili dostępny na courserze. Efekt ten jest tam jest przedstawiony jako jedna z większych przeszkód w nabieraniu nowych umiejętności - no bo jeśli wciąż powtarzamy te same schematy to trudno zaadoptować się do nowych podejść.

Żeby było bardziej naukowo wspomnimy o jednym z eksperymentów, który zademonstrował ów efekt. Generalnie była to scena ze szklanej pułapki 3 gdzie trzeba było odpowiednio przelewać wodę z jednego pojemnika do drugiego i do trzeciego.



Były dwie serie ćwiczeń gdzie jedna grupa rozwiązywała tylko drugą serię, w której istniało szybkie rozwiązywanie "równania" z użyciem tylko 2 z 3 zbiorników lub dłuższe z użyciem wszystkich trzech. Ta grupa bez problemów znalazła efektywne rozwiązanie z dwoma zbiornikami.

Druga grupa wcześniej robiła jeszcze inne ćwiczenie gdzie trzeba zawsze było użyć wszystkich trzech zbiorników i oni niestety w drugiej części eksperymentu zawsze używali wszystkich trzech zbiorników chociaż prostsze rozwiązanie z dwoma zbiornikami było w zasięgu reki.

Więcej info i dokładniejszy opis znajduje się w linku wikipedi poniżej - jest tam również wzmianka, iż uwaga od prowadzącego "nie bądźcie ślepi" pomagała znacznej ilość uczestników z grupy "z doświadczeniem" znajdować bardziej efektywne rozwiązanie.

Na końcu będzie więcej linków ale pewnie i tak nikt ich nie przeczyta także tutaj jest dobre miejsce na jeden najbardziej wyczerpujący :


I Kilka fajnych zeń cytatów bo jak mówiłem pewnie i tak nikt tych linków czytać nie będzie :

"When people test a theory, they look for evidence that is consistent with what they already believe rather than objectively assessing any evidence even if it might disconfirm their previously held belief"

"Einstellung refers to a person's predisposition to solve a given problem in a specific manner even though better or more appropriate methods of solving the problem exist. The Einstellung effect is the negative effect of previous experience when solving new problems. "

"The most famous example (...) is the Luchins' water jar experiment, in which subjects were asked to solve a series of water jar problems. After solving many problems which had the same solution, subjects applied the same solution to later problems even though a simpler solution existed"

ObjectAbstractFactoryBean

(a aktor nazywa się Sean Bean - czajcie jaki zajebisty się dowcip udał!!!)

Taka sytuacja - ktoś robi w Javie 6 od 5 lat. Ma masę przyzwyczajeń i wytrenowany mózg przez konkretną sytuację. Narysujmy sobie jak to może wyglądać koncepcyjnie na poziomie neuronów. Oczywiście słowo "koncepcyjnie" jest tu ważne i użyłem go po to aby uratować się od potrzeby dokładnego trzymania się jak te rzeczy wyglądają w rzeczywistości bo tego sam nie wiem - ale koncepcyjnie może wyglądać to tak :

I te trzy kuleczki mogą być siecią neuronową* (* - czytałem ostatnio, że nie neurony są ważne a komórki gleiste - czy jakoś tak - ale to wszystko co tutaj opisuję jest "koncepcyjne" także bez znaczenia), która została zbudowana przez wspomniane 5 lat programowania w Javie. I teraz Np. mamy sytuację, że trzeba zaimplementować zakup w sklepie. Siec neuronowa od razu podsunie ścieżkę rozwiązania "jak nazwać ten obiekt" - bo oczywiście TO musi być obiekt. A jak do tego używa się FRAMEWORKA to musi to być (Sean) Bean.

Jak z poziomu rozwiązywania problemów przejdziemy do bardziej ogólnego problemy nauki to zauważymy, EINsztellung stwarza wiele problemów bo mamy relatywnie ograniczone możliwości łączenia się nowych neuronów ze starą strukturą. I tak np. w konkretnym przykładzie uczymy się nowej koncepcji jaką jest Stream w Javie8 oraz jego zastosowania. Ponieważ nasz "arsenał poznawczy" składa się głównie z "Seana Beana Framework, Hibernate i Obiektów" to w ich kontekście będziemy definiować nowe pojęcie.

połączenie (koncepcyjne) jest jedno i jest słabe. Jest jedno bo mamy jedną (obiektowo-beanową) wizję świata. Tutaj pojawiają się takie koncepcje jak pamięć krótkotrwała, długotrwała itd ale w uproszczeniu z punktu widzenia nauki to jedno słabe połączenie może szybko zaniknąć przez co zapomnimy jak tego Streama używać i nauka pójdzie w las.A z punktu widzenia zastosowania praktycznego widzimy Stream jako nową "Kolekcję Obiektów" i tylko w tym kontekście nasz mózg będzie generował rozwiązania. Widziałem już kilka prezentacji ludzi z Oracla gdzie napominają by traktować Streamy jako "Strumienie wartości" a nie "Kolekcje obiektów".

Teraz bliżej przyjrzymy się tym trzem zielonym neuronom gdyż tworzą one pewien spójny zbiór uzupełniających się informacji - taki zbiór pojęciowy w naszej głowie nazywamy Chunkiem.

Chunk

Czym jest chunk : https://www.quora.com/What-is-chunk-or-chunking-in-learning

Chunk - Zbiór informacji, który tworzy jakąś pojęciową całość. Nasze koncepcyjne neurony wyhodowane przez 5 lat Javy stworzyły takiego właśnie pięknego Chunka. Obiekt,Klasa,Enkapsulacja,Getter,DAO - to wszystko w ramach tego chunka jest spójne i gotowe do obrony swoich granic (co często widać na forach). I teraz pytanie co jeśli mam inne chunki do których TAKŻE mogę odnieść nowe informacje? Czy to dobrze czy źle? Czy to pomoże czy przez zamieszanie przeszkodzi?

Z tego jak zrozumiałem ten cały mechanizm to jeśli jakąś nową koncepcję można połączyć z dwoma lub więcej niezależnymi chunkami to bardzo bardzo dobrze!. Raz, że powstaje więcej połączeń od nowej idei (ta niebieska kuleczka) a dwa, że następuje połączenie pomiędzy dwoma chunkami jako takimi, które były niezależne ponieważ okazuje się, że maja teraz ze sobą coś wspólnego. I wracając do przykładu ze Stream'ami - można je jednocześnie widzieć jako leniwa kolekcję obiektów jak i z drugiej strony jako abstrakcyjną formułę danych (w większości języków funkcyjnych podawany jest przykład z obliczeniem ciągu Fibonacciego przy pomocy streamów, które po prostu opisują w sposób jasny formułę obliczeń kolejnych elementów i to od użytkownika zależy gdzie w tej formule się zatrzyma). I teraz te koncepcje mogą zacząć się ze sobą łączyć. Czy Stream obiektów to Stream danych? Co musi się stać by obiekty nazwać Danymi? Same takie rozważania będą efektywnie utrwalać informacje w pamięci a do tego rozszerzać pole potencjalnych interpretacji nowych wiadomości.

Inny bardzo ciekawy przykład. Z mojego doświadczenia programiści C# często dużo szybciej łapią scalę niż programiści maintenance Javy 7,6,5. W C# lambdy mieli od dawna. Jak pokazywałem flatMapy to często słyszałem "a tak tak, u nas jest takie LINQ bardzo podobne do tego". Tymczasem gdy programista Javy widzi po raz pierwszy w życiu jak metoda przyjmuje funkcje i zwraca funkcje to czasami mu pęka mózg (w sensie głowa robi się bardzo czerwona i tak drga nerwowo dolna warga czyli chyba jakiś wylew)

W kontekście nauki po raz kolejny dobra wydaje się formuła CR (CR to Code Retreat a nie Chrystian Ronaldo) gdzie po prostu zabrania się rozwiązywania zadań w pewien sposób aby uczestnicy poznali przez to nowe sposoby podejść. I nie jest to prosty process bo nie raz uczestnicy fakt - że nie zdążyli z implementacją - odbierali bardzo osobiście i po to usuwa się ten kod aby ludzie odczepili ego od implementacji.

Aby nie było tak bardzo abstrakcyjnie poniżej kilka pomysłów na rozbudowę niezależnych programistycznych chunków

"Rozbuduj swą sieć z paleta języków"

Podobnie do Code Retreat niektóre języki usuwając pewne mechanizmy nadają ciekawe ograniczenia, które mogą nas zmusić do szukania nowych podejść.

Ocaml

Niedługo startuje kurs online: Ocaml Mooc. Jest to druga sesja i z tego co zrozumiałem z pierwszej to w Ocaml mamy do czynienia z pewnym negatywem Javy.

Koncepcyjnie w OCamlu kod wygląda tak

kod immutable
[DANGER DANGER DANGER ZONE]
kod mutable
[KONIEC DANGER ZONE]


W Javie w zasadzie zmiana wartości wszystkiego dookoła (nawet enkapsulowanego) jest (niekwestionowaną przez niektórych) codziennością także to by bardziej wyglądało w ten sposób :
[DANGER ZONE]
poczatek programu
[SAFE ZONE]
Zaimplementowane 15 mechanizmów z "Effective Java"
[KONIEC SAFE ZONE]
koniec programu
[DANGER ZONE]

No i jest zabawa "zaimplementuj rozwiązanie w sytuacji gdy kosztowne rytuały w kodzie wiążą się z mutowalnością".

Go

Podobno nie ma generyków. Jest to bardzo ciekawe dlaczego w 2016 jedna z przodujących firm IT na świecie reklamuje język bez generyków. Czy programowanie Go wygląda jak programowanie w Javie 4? A może poza javą ta koncepcja sprawdziła się lepiej?

Kotlin

Żeby nie było, że tylko programiści Javy mogą się czegoś nauczyć z innych języków. Jak chcesz w Scali pokazać na poziomie typów, że czegoś może nie być to Option,Option,Option... które i tak samo może być nullem. Tymczasem Kotlin ma bardzo wygodne zabezpieczenie przed nullem na poziomie typów bez wchodzenia w Monady i inne takie tam.

Haskell

Tu nima obiektów i wielu rzeczy nima - Ultimate Code Retreat dla programistów Javy.

Na jesieni ma być kurs : https://www.futurelearn.com/courses/functional-programming-haskell

myślenie lateralne

Przy okazji tego artykułu można poruszyć ten temat. Autorem koncepcji jest Bono ale nie ten z U2 bo Edward de Bono. Generalnie tutaj kompletnie zrywamy z rutyną mechanicznego myślenia. Np:

  • Jest problem z deploymentem
  • Jak byś go rozwiązał przy pomocy gabki, mikrofali i wody? (dokładnie tak)
  • Gabka coś wchłania i potem to gdzies można wycisnąć - może coś rozlazłego da się zebrać w kupę i przenieść w inne miejsce.
  • Mikrofala zwieksza temeperaturę i wysyła fale, zmienia naturę tego co w środku. Może kawałek aplikacji przenieść w miejsce gdzie będzie lepiej monitorowane
  • Woda zmywa brud. Może warto niepotrzebną cześć aplikacji usunąć przed deploymentem?
  • itd.
https://pl.wikipedia.org/wiki/My%C5%9Blenie_lateralne

Linki

środa, 22 czerwca 2016

Prymitywna szybkość i lekkość

W Java8 są takie wynalazki jak interfejs ObjIntConsumer a nie ma na przykład funkcji/metody zip. Metoda/funkcja zip (takie czasy, ze już nie wiadomo czy funkcja czy metoda) nie zwraca kodu pocztowego ale w teorii łączyłaby dwa strumienie/kolekcje i jest ona o tyle ciekawa, że np. w takich tutorialach do Haskella pojawia się co krok a szczególnie wygląda fantastycznie w niezwykle praktycznym przykładzie implementacji ciągu Fibonacciego na strumieniach (naprawdę jest fajne - nie ma stackoverflow i w ogóle.)

Brak zipa to (podobno) jedna z konsekwencji tego, że mamy Stream,IntStream,DoubleStream,LongStream - po co to i co to daje? Ano coś daje -> patrzymy dalej ->

Patrz stary jakie pomiary

Mam dwa takie chytrze spreparowane kawałki kodu aby pokazać co dają "prymitywne strumienie" w Java8. Pierwszy kawałek kodu jest przeznaczony dla "wszystko jest obiektem" lajfstajlu :

long numberOfElements = 1000000000;

Long result = Stream
             .iterate(0L, i -> i + 1)
              .map(i->i*i)
              .limit(numberOfElements)
              .reduce(0L,Long::sum);

Drugi zaś kawałek ma za zadanie pokazać jak na pierdyliard sposobów w javie można zdefiniować funkcje która dodaje jeden (w zależności od typu, fazy księżyca i aktualnej pozycji reprezentacji Polski w grupie)

long numberOfElements = 1000000000;

LongUnaryOperator step = i -> i + 1;
LongUnaryOperator intSquare = i -> i * i;
LongBinaryOperator sum = (i1, i2) -> i1 + i2;

long primitiveResult = LongStream
            .iterate(0, step)
            .map(intSquare)
            .limit(numberOfElements)
            .reduce(0, sum);

Dalsza część zabawy wygląda podobnie jak laborki w technikum - "wiemy, że prąd działa jak prąd bo mamyw domu pralkę i telewizję ale zrobimy pomiar by to potwierdzić. A jak się nie będzie zgadzało to będziemy robić do skutku".

Ogólnie pomiary wydajności to dosyć delikatna sprawa bo niby każdy powinien to umieć robić ale zazwyczaj przy większości prezentacji o "Performensie" czy jakimś tam tutorialu zazwyczaj jest teza, że większośc ludzi robi to źle. Sam robiłem to źle kiedyś bo kiedy skończyłem studia ze średnią 4coś i zerowa wiedzą jak działa JVM to brałem po prostu System.miliseconds a później klasyk "end minus start".

Później po odkryciu, że jest coś takiego jak JIT, kod Javy w sumie nie do końca jest interpretowany a suchary o tym, że java jest wolna są powielane przez programistów c++ , którzy swoja tożsamość budują nad ilością kontroli, którą sprawują nad kawałkiem pamięci - po tym wszystkim okazało się, że w sumie trudno stwierdzić czy pomiary wydajność kodu javy zrobiło się dobrze bo nie wiadomo jak ma się to co jest napisane do tego co chodzi tam w rantaimie.

(Dodatkowo dobiło mnie kiedyś jak przeczytałem gdzieś, że większość pomiarów czasu odpowiedzi strony www mierzy w dużej mierze jaka działa infrastruktura interentu a nie jak działa strona www ale już nie pamiętam i nie moge tego znaleźć teraz)

No ale teraz jest ratunek, jest narzedzie - jest nadzieja!

jmh - teraz i ty możesz mówić, że umiesz robić pomiary!

Wydaje mi się, że najlepiej wejść tutaj : http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/ i pooglądać sobie przykłady. Ja na początku trafiłem na tutorial z 2013 gdzie jakiś typ opisywał krok po kroku jak to konfigurować. Ponieważ otrzymywałem w wyniku czas wykonania 0 to na początku popadłem w depresję, że nawet jednej adnotacji nie umiem poprawnie dodać do kodu.

Na szczęście okazało się, że bezmyślnie kopiując kod używałem wersji 0.1 jmh. Po dodaniu zależności do 1.12 ku memu zachwytowi zaczęło to działać. A działać to oznacza, że udało się odpalić coś takiego :

@Benchmark
@BenchmarkMode(Mode.AverageTime)
public void boxed(){
   long numberOfElements = 1000000000;

   Long result = Stream
                .iterate(0L, i -> i + 1)
                .map(i->i*i)
                .limit(numberOfElements)
                .reduce(0L,Long::sum);


    System.out.println(result);
}

I dało końcu długo oczekiwany wynik :

# JMH 1.12 (released 81 days ago)
# VM version: JDK 1.8.0_91, VM 25.91-b14
# Warmup: 20 iterations, 1 s each
# Measurement: 20 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op

# Run progress: 0.00% complete, ETA 00:00:40
# Fork: 1 of 1
# Warmup Iteration   1: 3338615082255021824
13.917 s/op
# Warmup Iteration   2: 3338615082255021824
13.359 s/op
(...)
Iteration   1: 3338615082255021824
11.543 s/op
Iteration   2: 3338615082255021824
11.345 s/op
Iteration   3: 3338615082255021824
11.068 s/op
Iteration   4: 3338615082255021824
10.899 s/op
Iteration   5: 3338615082255021824
13.932 s/op
Iteration   6: 3338615082255021824
13.719 s/op
Iteration   7: 3338615082255021824
13.830 s/op
(...)

Result "boxed":
  13.295 ±(99.9%) 0.937 s/op [Average]
  (min, avg, max) = (10.899, 13.295, 14.231), stdev = 1.080
  CI (99.9%): [12.358, 14.233] (assumes normal distribution)


# Run complete. Total time: 00:08:42

Benchmark      Mode  Cnt   Score   Error  Units
JMHTest.boxed  avgt   20  13.295 ± 0.937   s/op

Jak komuś nie chce się czytać to wyszło średnio : 13 sekund

Po wsadzeniu kodu dla strumienia Longów :

Result "primitive":
  1.580 ±(99.9%) 0.009 s/op [Average]
  (min, avg, max) = (1.568, 1.580, 1.601), stdev = 0.011
  CI (99.9%): [1.571, 1.589] (assumes normal distribution)


# Run complete. Total time: 00:01:04

Benchmark          Mode  Cnt  Score   Error  Units
JMHTest.primitive  avgt   20  1.580 ± 0.009   s/op
Wyszło z 10 razy szybciej. Pytanie czy te pomiary są poprawne? Przede wszystkim nie zastosowałem się do zalecenia ze strony, żeby nie puszczać tego z IDE no i trudno mi stwierdzić czy ten kod jest na tyle "chytry", że JIT nic tam nie pousuwa. Wygląda na to, ze jmh jest przynajmniej wystarczająco sprytne by to jakoś odpowiednio odpalać tak, że iteracje rozgrzewania nie różnią się jakoś specjalnie czasem od iteracji wykonania to chyba ten czas 13s i 1.5s jest już po optymalizacji. Chyba. Temat jest ciekawy i wart zbadania.

I jeszcze sterta

Aby wpis był ciekawszy potrzebne są jakieś obrazki.

Tak wyglądał wykres zajętości pamięci pokazany przez visualvm dla przykładu ze strumieniem Łobiektów. A poniżej przykład dla strumienia prymitywnych Longów. Warto zauważyć, że operacje na prymitywach nie wywołały nawet pierdnięcia na procku.

Jak wyglądał pomiar? Zapauzowałem program na wczytywaniu znaku, odpaliłem visualvm i dalej program. Czy to jest dobrze? Nie wiem. Wykres na sprawozdanie wyszedł zgodny z przewidywaniami teoretycznymi.

Dlaczego nie ma zipa?

Jest gdzieś na necie prezentacja gościa z Oracla gdzie tłumaczy, że gdyby chcieli zrobić zipa dla wszystkich kombinacji różnych strumieni to wyszłoby tego z kilkanaście różnych wariacji. A gdyby chcieli dorzucić coś jeszcze to ilość wymknęła by się spod kontroli. Niby wszystkie te strumienie dziedziczą z czegoś co nazywa się BaseStream ale widocznie nie jest to takie abstrakcyjne aby można było napisać zip(BaseStream s1,BaseStream s2)

No i te 4 funkcje - chociaż wyglądają identycznie - to z punktu widzenia javy w kontekście typu mają ze sobą wspólnego NIC.

UnaryOperator<Integer> f1=i->i+1;
IntUnaryOperator f2=i->i+1;
LongUnaryOperator f3=i->i+1;
DoubleUnaryOperator f4=i->i+1;

Inne

Myślałem, że trochę przykozaczę i pokażę alternatywę bo w Scali są takie adnotacje

trait Function1[@specialized(scala.Int, scala.Long, scala.Float, scala.Double) -T1
I to według sprawi, że kompilator wygeneruje prymitywne wersje funkcji tak jak trzeba czyli gdy mamy :
class Specialization{
  val f:Function[Int,Int]=i=>i+1
}
To po dekompilacji faktycznie używa prymitywnych intów :
public final class mails.Specialization$$anonfun$1 extends scala.runtime.AbstractFunction1$mcII$sp implements scala.Serializable {
  public static final long serialVersionUID;
  public final int apply(int);
  public int apply$mcII$sp(int);
  public final java.lang.Object apply(java.lang.Object);
  public mails.Specialization$$anonfun$1(mails.Specialization);
}

Tej adnotacji nie ma w klasie Stream toteż w sumie tak trochę niepewny rezultatu odpaliłem ten kod :
val result=Stream
      .iterate(0L)(_+1)
      .map(i=>i*i)
      .take(1000000000)
      .reduce(_+_)

    println(result)
I musiałem przerwać bo youtube mi się zaczął przycinać. Temat ciekawy i do dalszego zgłębienia - być może ja robię coś źle (pewnie tak) a być może scala 2.12 która ma chodzić już na Javie 8 gdzieś tam pod spodem te IntStreamy ładnie wykorzysta i w przyszłości "samo z siebie" zadziała.

Próbowałem też z innym mniej popularnym językiem :

execute :: Int
execute = sum $ take 1000000000 $ map (+1) [1..]
Ale to już tak zagrzało kompa, że resztę testów zostawię na zimę...

Warsztat dla początkujących

Generalnie brak znajomości tego co się dzieje "pod spodem" trochę utrudnia programowanie. Także w duchu wspólnej edukacji meetup 5 lipca

Warsztaty Java 8 Streams - Wstęp - 5 lipca

Podsumowanie

To jest miejsce na jakieś mądre podsumowanie - jak mi cos kiedyś przyjdzie do głowy to może uzupełnię.

środa, 8 czerwca 2016

Dziwna metoda reduce

Głównym językiem tego posta będzie Java8 której pisanie zużywa klawiaturę jak mało który język.. Ale będzie też o tzw. "sztuce upadania". Był o tym jakiś czas temu artykuł w magazynie Coaching - numer listopad-grudzień 2015. Konkretnie artykuł nazywa się "Dlaczego warto ćwiczyć upadanie" i w formie wywiadu opowiada o toksycznej zdaniem autorów (i nie tylko) obecnej kulturze, gdzie każdy się musi porównywać z człowiekiem obok, zawsze ale to zawsze musi się wszystko udawać a jak się nie udaje to jest tragedyja nad tragedyje.

No i przez to ludzie się boją eksperymentować i tkwią w czymś popularnie zwanym "strefa komfortu". Później w artykule jest porównanie do sztuk walki gdzie ćwiczy się pady i oswaja z sytuacją "gleby", że ta już nie jest straszna itd itp. I właśnie w tym duchu będzie ten post gdzie nie wszystko będzie działało. A w zasadzie cały czas nie będzie działało i dopiero cos pod koniec zadziała.

Ale od poczatku...

Gdzie jest mój Fold????

W Scali i nie tylko mamy na kolekcjach kilka metod, które potrafią zamienić kolekcję w "jedną rzecz". Ta "jedna rzecz" to może być intem jak np. sumujemy cyfry w liście lub nawet i inna kolekcja jeśli chcemy elementy przekształcić i (lub) przepisać. Metody nazywają się "fold,foldRight,foldLeft" i wyglądają mniej więcej tak :

 def foldLeft[B](z: B)(op: (B, A) => B): B 

Mamy element zerowy oraz przepis na doklejanie kolejnych kawałków .Elementy to A zaś element zerowy to B także nie muszą mieć ze sobą za wiele wspólnego. Metoda jest na tyle potężna i uniwersalna, ze można przy jej pomocy zaimplementować większość możliwych operacji na kolekcjach i nawet ma swoją stronę an wikipedii : https://en.wikipedia.org/wiki/Fold_(higher-order_function)

Czy ktoś z was ma swoją stronę na wikipedii? No właśnie...

No i w Javie8 tego nima (albo ja nie mogę tego znaleźć). Za to jest coś innego. W sensie inna operacja i pomimo, że wygląda dziwnie to niesie ze sobą coś równie silnego.

Coś innego

Pierwszy wariant to reduce z tzw. BinaryOperatorem, który jest funkcją (A,A)=>A tyle, że brzmi bardziej obiektowo i zwiększa pule typów dodanych do Javy8

Optional<T> reduce(BinaryOperator<T> accumulator);
Zwraca Optional gdyż w przypadku pustego Streama nic tam nie ma (czyżby rym?).

Druga opcja dostarcza element domyślny nazwany identity i już nie potrzeba Optionala bo wiemy, ze tam coś zawsze jest.

T reduce(T identity, BinaryOperator<T> accumulator);
To co jest warte uwagi to to, że tam je ino tylko jeden typ T.

No i na pewno teraz będzie jakaś sygnatura funkcji z identity o innym typie. No ma sens by tak było.Ma to sens .. ale nima. No prawie nima bo od razu jedziemy z czymś takim.

<U> U reduce(U identity,
                 BiFunction<U, ? super T, U> accumulator,
                 BinaryOperator<U> combiner);
To nie jest fold. To jest bardziej skomplikowane. Ale tam dzieje się coś ciekawego.

Nauka w szeroko pojętym znaczeniu

Wymiar ciekawości rozciąga się poza Javę 8 gdyż teraz na Courserze trwa kurs o programowaniu równoległym w Scali - tak w Scali - i tam tę sygnaturę wyprowadzili krok po kroku. Oczywiście na normalnych funkcjach bez wynalazków "BinaryOperator" czy "BiFunction". Ale to jest znowu taka uwaga aby edukację skalować też w szerz a nie tylko wzwyż - czyli zamiast maksymalnego ubijania jednej tylko technologi (albo nawet i kawałka technologi) tylko dlatego, że akurat "w tym robię an co dzień", poczytać trochę rzeczy pobocznych. Cały czas uważam, że jak kto chce lepiej zrozumieć naturę funkcji w Javie to powinien pouczyć się trochę Haskella bo tam te mechanizmy są naturalne.

Bo młodość jest po to by eksperymentować

Od dzieciństwa bardzo marzyłem by zebrać w jednej kolekcji 10 kolejnych liczb parzystych poczynając od zera. I dzisiaj to zrobimy :

        List<Integer> even = new LinkedList<>();


        List<Integer> result1 = Stream
                .iterate(0, e -> e + 1)
                .filter(e -> e % 2 == 0)
                .limit(10)
                .reduce(even,
                        (partial, elem) -> {
                            partial.add(elem);
                            return partial;
                        },
                        (partial1, partial2) -> {
                            partial1.addAll(partial2);
                            return partial1;
                        }
                );

Generalnie obliczenia równoległe tego typu nie mają żadnego sensu bo narzut samej maszynerii będzie dużo większy niż gdyby jeden procek to szybko policzył ale po raz kolejny podkreślimy role edukacyjną tego problemu (także podkreślamy). Metoda reduce przyjmuje trzy argumenty :

  • Element początkowy - tutaj przekazujemy standardową Listę z Javy, która nazywa się "mutowalna" chociaż pewnie takiego słowa w języku polskim nie ma. Programowanie równoległe i kolekcja mutowalna - no co może pójść źle?
  • przepis jak do kolekcji dokładać nowe elementy - pewnie te kawałki pójdą równolegle
  • przepis jak połączyć rezultaty cząstkowe

No i na pierwszy rzut oka działa :

System.out.println(result1);

//[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
Ale działa tylko dlatego, że źle tego używam. No i pytanie jaka jest tam rola pierwszego parametru skoro i tak dostajemy rezultat z całego działania. Wszystko się wyjaśni kiedy zaczniemy tego mechanizmu używać poprawnie i dostaniemy błąd.

Równoległe

Poniższy rysunek przedstawia jak "mi się wydawało, że to działa". I nawet jeszcze dzisiaj zobaczymy jak przy pomocy pewnej listy reduce tak własnie zadziała. Ale to będzie specjalna lista a nawet nie lista tylko Wektor a w sumie Vector ale nie ten z Javy tylko inny. A ta teraz to standardowa lista - taka mutowalna - wrzucona między kilka wątków - czeka nas rzeź...

Spróbujmy niezależnie zdefiniować dwie funkcje używany w poprzednim przykładzie :

        Function<List<Integer>, Integer, List<Integer>> accumulator = (partial, elem) -> {
            System.out.println("accumulator : " + Thread.currentThread().getName());
            partial.add(elem);
            return partial;
        };

        BinaryOperator<List<Integer>> combiner = (l1, l2) -> {
            System.out.println("combiner : " + Thread.currentThread().getName());
            l1.addAll(l2);
            return l1;
        };
Dodamy informacje o wątku w którym działają oraz dokonamy obserwacji, że Lista w Javie jest dziwna bo operacje nań wykonane wcale nic nie zwracają i przez to trzeba dodawać dodatkową linię z return. Oczywiście - tutaj mały spoiler - ta obserwacja pojawiła się gdyż cały czas źle tego wszystkiego używamy. Zaraz dojdzie do nas co i jak.

I teraz taki kod:

        List<Integer> even2 = new LinkedList<>();
        List<Integer> result2 = Stream
                .iterate(0, e -> e + 1)
                .filter(e -> e % 2 == 0)
                .limit(10)
                .reduce(even2, accumulator,combiner);


        System.out.println("result2 : "+result2);
        System.out.println("even2 : "+even2);
Generuje taki rezultat :
accumulator : main
accumulator : main
accumulator : main
accumulator : main
accumulator : main
accumulator : main
accumulator : main
accumulator : main
accumulator : main
accumulator : main
result2 : [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
even2 : [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
Czyli generalnie wszystko odbywa się w jednym wątku i combiner w ogóle nie jest użyty.

Można to łatwo naprawić dając "parallel" w ciąg wywołań strumienia :

 .limit(10)
 .parallel()
 .reduce(even2, accumulator,combiner);
No i naprawiliśmy problem z ilością wątków i wywołaniem "combinera" :
accumulator : ForkJoinPool.commonPool-worker-3
accumulator : ForkJoinPool.commonPool-worker-4
accumulator : ForkJoinPool.commonPool-worker-6
accumulator : ForkJoinPool.commonPool-worker-5
accumulator : main
accumulator : ForkJoinPool.commonPool-worker-1
accumulator : ForkJoinPool.commonPool-worker-6
accumulator : ForkJoinPool.commonPool-worker-2
accumulator : ForkJoinPool.commonPool-worker-4
accumulator : ForkJoinPool.commonPool-worker-3
combiner : ForkJoinPool.commonPool-worker-4
combiner : ForkJoinPool.commonPool-worker-2
combiner : ForkJoinPool.commonPool-worker-1
combiner : ForkJoinPool.commonPool-worker-2
combiner : ForkJoinPool.commonPool-worker-4
combiner : ForkJoinPool.commonPool-worker-3
combiner : ForkJoinPool.commonPool-worker-2
combiner : ForkJoinPool.commonPool-worker-3
I przy okazji popsuliśmy wszystko inne :
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:598)
    at java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:677)
    at java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:735)
    at java.util.stream.ReduceOps$ReduceOp.evaluateParallel(ReduceOps.java:714)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233)
    at java.util.stream.ReferencePipeline.reduce(ReferencePipeline.java:484)
(...)   
Caused by: java.lang.ArrayIndexOutOfBoundsException: 240
    at java.util.LinkedList.toArray(LinkedList.java:1053)
    at java.util.LinkedList.addAll(LinkedList.java:408)
Jest ReduceOps, AbstractPipeline i kilka innych rzeczy, których przeznaczenia można się jedynie domyślać. Tak czy inaczej nie działa.

To jest ten moment kiedy zaczynasz się zastanawiać : "A może przed włączeniem trzeba było przeczytać dokumentację?". Dokumentacja : https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html w zasadzie ma wydzieloną sekcję Mutable reduction i jasno opisuje, że reduce nie jest do tego. Do rzeczy mutowalnych jest collect a reduce do niemutowalnych. Zaraz przejdziemy do collect ale wcześniej jeszcze jeden eksperyment.

Persystentna Struktura Danych

(słowa persystentna też pewnie nie ma w słowniku)

Użyjemy Vector gdyż podobno twórca Javaslang brał inspiracje ze Scali a w Scali Vector jest zaimplementowany jako drzewo i czasem operacje działają na nim szybciej niż na liście... ale czasem wolniej... generalnie trzeba zawsze mierzyć ... dobra tutaj używamy Vectora by było bardziej kolorowo a nie tylko Lista i Lista

import javaslang.collection.Vector;
Vector<Integer> even = Vector.empty();

Od razu nasze funkcje się uproszczą gdyż operacje na "trwałych" (może to jest to słowo) strukturach danych zwracają nowy rezultat bo inaczej nie miałoby to żadnego sensu.

       BiFunction<Vector<Integer>,Integer,Vector<Integer>> accumulator= (partial, elem)->{
            System.out.println("accumulator : "+Thread.currentThread().getName());
            return partial.append(elem);
        };

        BinaryOperator<Vector<Integer>> combiner=(l1, l2)->{
            System.out.println("combiner : "+Thread.currentThread().getName());
            return l1.appendAll(l2);
        };

I teraz tak napisany kod :

       Vector<Integer> result = Stream
                .iterate(0, e -> e + 1)
                .parallel()
                .filter(e -> e % 2 == 0)
                .limit(10)
                .reduce(even, accumulator,combiner);


        System.out.println("even : "+even);
        System.out.println("result  : "+result);
Działa poprawnie - nie wiadomo czy szybko - ale poprawnie.
accumulator : ForkJoinPool.commonPool-worker-5
accumulator : ForkJoinPool.commonPool-worker-2
accumulator : ForkJoinPool.commonPool-worker-4
accumulator : ForkJoinPool.commonPool-worker-7
accumulator : ForkJoinPool.commonPool-worker-6
accumulator : main
accumulator : ForkJoinPool.commonPool-worker-3
accumulator : ForkJoinPool.commonPool-worker-1
accumulator : ForkJoinPool.commonPool-worker-4
combiner : ForkJoinPool.commonPool-worker-7
combiner : ForkJoinPool.commonPool-worker-2
accumulator : ForkJoinPool.commonPool-worker-5
combiner : ForkJoinPool.commonPool-worker-1
combiner : ForkJoinPool.commonPool-worker-5
combiner : ForkJoinPool.commonPool-worker-7
combiner : ForkJoinPool.commonPool-worker-5
combiner : ForkJoinPool.commonPool-worker-5
combiner : ForkJoinPool.commonPool-worker-1
combiner : ForkJoinPool.commonPool-worker-1
even : Vector()
result  : Vector(0, 2, 4, 6, 8, 10, 12, 14, 16, 18)

Ten przykład był po to aby czytelnik zdobył lepszą intuicję w rozumieniu różnic pomiędzy "trwałymi" i "nietrwałymi" (tak to an pewno te słowa!) strukturami danych oraz, że trwałe/niemutowalne nie musi koniecznie oznaczać inta - a można tak pomyśleć bo wiele przykładów na necie liczy sumę, zawsze sumę czegoś.

collect

Metoda collect wspomniana w dokumentacji wygląda tak :

   <R> R collect(Supplier<R> supplier,
                  BiConsumer<R, ? super T> accumulator,
                  BiConsumer<R, R> combiner);
Supplier i consumer to klasa tzw. "funkcji dziwnych". Pierwsza tworzy coś z niczego a druga nic z czegoś czyli po prostu połyka argument i generuje jakiś "efekt uboczny". No i w sumie to jest to czego byśmy oczekiwali przy operacji na nietrwałej strukturze danych.
        List<Integer> even=new LinkedList<>();

        BiConsumer<List<Integer>,Integer> accumulator= (partial, elem)->{
            System.out.println("accumulator : "+Thread.currentThread().getName());
            partial.add(elem);
        };

        BiConsumer<List<Integer>,List<Integer>> combiner=(l1, l2)->{
            System.out.println("combiner : "+Thread.currentThread().getName());
            l1.addAll(l2);
        };
Kod wygląda podobnie do przykładu z reduce ale już nie trzeba na siłę zwracać listy po dodaniu elementu.

No to wiooooo :

List<Integer> result = Stream
                .iterate(0, e -> e + 1)
                .parallel()
                .filter(e -> e % 2 == 0)
                .limit(10)
                .collect(() -> even, accumulator,combiner); 

System.out.println(result);
i znowu jebłoooo
combiner : ForkJoinPool.commonPool-worker-3
accumulator : ForkJoinPool.commonPool-worker-6
accumulator : ForkJoinPool.commonPool-worker-5
combiner : ForkJoinPool.commonPool-worker-5
combiner : ForkJoinPool.commonPool-worker-5
combiner : ForkJoinPool.commonPool-worker-6
combiner : ForkJoinPool.commonPool-worker-2
combiner : ForkJoinPool.commonPool-worker-6
combiner : ForkJoinPool.commonPool-worker-2
combiner : ForkJoinPool.commonPool-worker-2
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    

Działająca wersja

No to w końcu coś co działa :

(...)
.collect(LinkedList::new, accumulator,combiner);

(...)
combiner : ForkJoinPool.commonPool-worker-4
combiner : ForkJoinPool.commonPool-worker-4
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

Generalnie te porażki i potknięcia doskonale tłumaczą po co potrzebny jest Supplier. Każdy z niezależnych wątków musi mieć swój własny "nietrwały" półprodukt aby nie wchodzić sobie w drogę.

Collectors

Jak ktoś już Javą8 się bawił to wie, że jest 100 razy prostszy sposób na osiągnięcie tego wyniku :

.collect(Collectors.toList());

i należy go stosować i zasada DRY tutaj działa. Zapisywanie do listy to częsty i standardowy problem ze standardowym rozwiązaniem - w pracy go używaj. Tutaj słowo klucz to edukacja. Edukacja jest potrzebna aby usunąć symbolikę "abstrakcji kolektora". To trochę tak jak z Hibernate używanym przez ludzi nie znających baz danych. Czasem jak "działa ale nie wiadomo dlaczego" - to jesteśmy o krok od katastrofy. Bo jak nie wiadomo jak to działa to skąd wiadomo czy ten kawałek kodu znaleziony na necie rozwiąże akurat mój problem bez generowania pięciu innych?

Podobnie można podejść do tematu dokumentacji. Tak trzeba czytać. Ale jak sobie tak sam dla siebie eksperymentujesz - to jest to dużo lepszą formą nauki. Jeśli tylko nie pracujesz z zapalnikiem bomby atomowej - zrób sobie laboratorium. Postaraj się przewidzieć, że coś jebnie i faktycznie zweryfikuj. Na pewno w mózgu zostanie więcej niż gdy wszystko się od razu uda. Po to właśnie na Code Retreat usuwa się kod, żeby ludzie przypadkiem nie pomyśleli, że chodzi o kod a nie o czynność jego tworzenia. Jedyne co ma zostać po ćwiczeniu to nowe ścieżki pomiędzy neuronami.