niedziela, 29 listopada 2015

Warsztaty z Dataframe i wyzwania edukacji

Problem : Jak zainteresować słuchaczy czymś co z wierzchniej perspektywy przypomina zwykłą tabele SQL ale jest w sumie czymś innym i stanowi bazę do zgłębiania DataScience na Sparku?

Plan na warsztat jest gotowy i można się zapisywać----> MIGAJACE BOMBKI STRONA MEETUPA MIGAJACE BOMBKI

Zaś materiały są tutaj ---> https://pawelwlodarski.gitbooks.io/workshops/content/spark_dataframes.html

By było ciekawie można..

Nawiązać do rzeczy już poznanych. Ten motyw pojawia się w książce "Power of habit". Podobno przebój OutKast "Hey Ya" wcale nie był przebojem ale wytwornie, które mogą wywierać nacisk na radiostacje ten nacisk wymogły i HEYYA było puszczane pomiędzy przebojami PRZEBOJ<-->HEYYA<-->PRZEBOJ i ludzie się przyzwyczajali.

Dlatego tez warsztat zaczniemy od nieśmiertelnego przykładu zliczania słów z pliku LICENSE. To akurat z pełną powagą powinno ładnie pokazać porównanie pomiędzy RDD i tym co oferuje Dataframe. Ćwiczenia będziemy robić krok po kroku z dokładnym badaniem web UI.

Można dać jakieś ciekawe dane

  • polityka - zły pomysł bo ludzie się pozabijają
  • Coś z wóda bo to zawsze jest śmieszne - ale może lepiej nie bo to niezdrowe
  • Czołgi,pistolety itd - no tez dzisiaj nie da rady
  • To może jednak wóda? Ale nie mogę znaleźć CSV z danymi o wódzie na necie także nie wóda :(
  • No to będzie piłka nożna!

Będzie wczytywanie danych bezpośrednio z CSV, wykrywanie schemy oraz datascience przy obliczaniu statystyk piłkarskich. Czyli na przykład pokażemy jak łatwo znależć najbardziej i najmniej skuteczną drużynę piłkarską + możemy znaleźć co tylko dusza zapragnie.

Można pokazać Sparka w kontekście całego ekosystemu BigData

W tym celu trzeba sobie zainstalować quickstart z cloudery. Powinno to być dosyć łatwe i od razu rozwiązuje problem z tym, ze Spark 1.5 nie działa na windowsie. W trakcie ćwiczeń zaczytamy dane z Hive a Hive zrobił Facebook a Facebook używa realnie Haskella i OCamla co na razie nic nie wnosi ale jest ciekawe

Także to może być dla wielu pierwsze spotkanie z szerszym ekosystemem hadoopa.

Można trochę pokazać języka R

I pokażemy bo R jest od DataScience. Bardzo ciekawy język, z którego pożyczono Dataframes. Przykłady do samodzielnego wykonania możesz znaleźć tutaj --> http://pawelwlodarski.blogspot.com/2015/09/dataframes.html

Można nawiązać do FP

Bo FP jest zawsze ciekawe. Mamy coś takiego :

scala> val fun:Int=>Int = x=>x+1
fun: Int => Int = <function1>

scala> :t udf(fun).apply _
Seq[org.apache.spark.sql.Column] => org.apache.spark.sql.Column

No i teraz możemy zobaczyć, że udf to funkcja, która zamienia (A=>B) => UserDefinedFunction

UseDefinedFunction z powodów praktycznych ma varargsa w apply i poprzez wywołanie (chyba ETA expansion)
UserDefinedFunction.apply _ dochodzimy do (A=>B) => Seq[Column] => Column

Teraz siłą wyobraźni można napisać coś takiego

scala> val lift :(Int=>Int) => (Column=>Column) = f=> a => udf(f).apply(a)
lift: (Int => Int) => (org.apache.spark.sql.Column => org.apache.spark.sql.Column) = <function1>

scala> :t lift
(Int => Int) => (org.apache.spark.sql.Column => org.apache.spark.sql.Column)

(Int => Int) => (Column => Column) to taki naciągany Functor ale zawsze coś zabawnego z Teorii Kategorii. Także zapraszam. --> http://www.meetup.com/Java-User-Group-Lodz/events/225016091/

niedziela, 22 listopada 2015

Funkcyjne Code Retreat

Mięsień nie używany zanika ale mięsień używany ale nie ćwiczony i pozbawiony zróżnicowanych bodźców nie staje się silniejszy. Są różne "mięśnie programistyczne" które możemy ćwiczyć i jednym z nich jest mięsień funkcyjny - dosyć nowy, niedawno odkryty na lokalnych siłowniach i generujący masę hejtu tudzież frustracji u tych, którzy przy jego pomocy chcą unieść problemy cięższe od tych spotykanych do tej pory.

Co roku na CodeRetreat ćwiczymy tworzenie dobrego kodu przy pomocy.... tworzenia dobrego kodu (taka idea ćwiczenia). Klasycznie CR używa wielu technik dobrego kodu OOP i to jest bardzo wskazane bo wiele osób zanim wejdzie w ogóle na płaszczyznę "Object Oriented vs. Functional" musi zrozumieć, że na co dzień pisze szalenie złożony kod proceduralny zbudowany z zagnieżdżonych ifów i pętli.

W trakcie tegorocznego łódzkiego CodeRetreat głównym prowadzacym był Adrian Bolboaca a ja tym razem pełniłem role ciecia do pomocy co zdjęło kilka obowiązków z moich barków i pozwoliło obserwować co dokładnie robią ludzie. Ostatnia sesja na życzenie zgromadzonych była dłuższa i polegała na programowaniu funkcyjnym w języku nie funkcyjnym.

Te funkcyjne konstrukcje są już w Javie8 i w końcu trafią i do twojego korpo także można zacząć ćwiczyć i dziś. A z tego co widziałem trzeba zacząć ćwiczyć FP - oj trzeba.

Toteż poniżej plan ćwiczeń "Funkcyjnego Code Retreat"

Funkcyjny Code Retreat

Idea jest taka by ćwiczenia poza wartością edukacyjną miały zajebiste tytuły podkreślające epickość przerabianego materiału. Opisy są po angielsku z błędami bo akurat tak sobie zapisałem a nie chce mi się tłumaczyć.

  1. Session 1 – „Values are Forever” (enter world when 1 has always value of 1)
    • Theory : explain that most of people already use functional structures in GIT and that immutable data is was already one of good practices in 2006 in Effective Java (why?) Only final variables. Only immutable data structures. This is a warm up session but it’s already give some functional constraints.
  2. Session 2 – “Citizen Function” – (functions as a first class citizens)
    • Theory : Origins of OOP, why you need to encapsulate mutable state, when anemic data is good, what are benefits of separations operations from data? All existing logic can be only defined as Function (Function in Java, val fun:A=>B in scala etc. ) Data is represented by structures. No word “this” allowed (either explicitly nor implicitly)
  3. Session 3a – “Pure Stuff” – (an injection of mathematical purity)
    • Theory: Referential transparency, Unit, Void and () types. Only pure functions allowed
    Session 3b – “Fix the Billion dollar Mistake” (null,NullObjectPattern > /dev/null)
    • Theory: Referential transparency, Unit, Void and () types. How to preserve functional purity in context of value absence. Mindfuck : List as a generalization of option. Use Either Option Or Maybe Optional
  4. Session 4 – “Curry On” – (purest Dependency Injection)
    • Theory: Dependency injection out-of-the box. Only functions with one parameters allowed
  5. Session 5 – “Back to reality” – (mathematical purity is cool but you’ve just got a call from reality)
    • Theory: Functors basics, side effects in the world of functional purity. Log each operation to the console. Each function has to stay pure.

Kiedy?

Biorąc pod uwagę ilość dobrych rzeczy (jak święta) i ilość złych rzeczy (jak nie święta), które mają sie wydarzyć w najbliższym czasie to takie spotkanie powinno się udać zrobić gdzieś w pierwszej połowie stycznia.

czwartek, 12 listopada 2015

Code Retreat 2015 - nie bój się nauki

Global Day of Code Retreat już w tę sobotę ("będzie" albo "był" jeśli nie zdążyłem tego napisać i opublikować). Zapisać może się każdy a link jest tutaj --> Link na meetupie. I oczywiście jak co roku może pojawić się pytanie "ale po co?"

Niestety większość ludzi mylnie utożsamia naukę z ilością czasu jaką poświęcili na daną czynność. Czyli np. "mam 3 lata doświadczenia" jest utożsamiane z "przez trzy lata robiłem w zasadzie to samo, zebrałem ekspa i mogę przejść na super sajana 2". Jeśli się robi przez 3 lata to samo to raczej wiedza nie rośnie a pielęgnujemy te same nawyki. I nie wiadomo czy to jest dobre bo nie wiadomo czy same nawyki jako takie były dobre.

I tak np. ostatnio była dyskusja o tym jakie podejście jest lepsze , w czym i jak pisać itd - czyli dyskusja numer 13719273 o dyskusji. Pojawił się tam jednak ciekawy wątek o tym kiedy tak naprawdę - trochę duży poziom abstrakcji - można stwierdzić, że ktoś ma prawo twierdzić, ze coś jest lepsze od czegoś innego (no mówiłem, że duży poziom abstrakcji)

I pada takie sformułowanie "wypowiadam się o X bo napisałem w tym 14000 linii". I w sumie znałem kiedyś jednego takiego gościa co napisał w Javie 14000 - problem polegał na tym, że on to napisał je wszystkie w jednej klasie :D:D . W sumie to on i pewnie miał doświadczenia na 100.000 linii i na jakieś 6 klas ale znów specjalnie to jest uwypuklone by pokazać, że to wcale nie było dobre. Jak ktoś pisze przez 3 lata nieczytelny proceduralny kod to faktycznie ma 3 lata doświadczenia w pisaniu nieczytelnego proceduralnego kodu.

Cykl Kolba

Spójrz na ten rysunek :

Albo ten :

I teraz zastanów się czy tak podchodzisz do tego co robisz (ha! piszę w drugiej osobie tak jak ci smutni trenerzy rozwoju osobistego "gdy czytasz te słowa zastanów się czy jesteś tak efektywny jak mógłbyś być" ) czyli np. zastanawiasz się krytycznie (ale pod katem rozwojowym) co zrobiłeś źle i co możesz zrobić lepiej (brzmi trochę jak agile za czasów zanim korporacje go nie zjebały) czy może to taki model ala gierki RPG "robie robie robie, experience rośnie , to chyba samo się będzie się robić lepiej jak nowy level dostane co nie?"

no raczej nie

Generalnie kiedy coś robimy - powiedzmy na poziomie A1 to musimy wejść jeden poziom abstrakcji wzwyż na poziom A2, a nawet nie - musimy wejść jeszcze wyżej. Musimy wejść na poziom A3 gdyż na A2 możemy zobaczyć co robimy a na A3 - uwaga bardzo ważne - możemy zobaczyć siebie jak obserwujemy co robimy - bardzo bardzo ważne.

Przeszkoda w samokształceniu numer 1

Dla tych co mają samochody efekt jest znany - "ci co jadą wolniej ode mnie to cioty, ci co szybciej to wariaty!". Ja się spóźnię to wina znowu jebanego kierowcy autobusu, babci na przystankach, korków i kretów co mi an drodze wykopały kopce. jak ktoś inny się spóźni to oczywiście nie umie sobie planować czasu i jego "analytic skils" są słabe bo mógł wyjść wcześniej itd. Można sobie poczytać więcej o np. tutaj --> http://blog.krolartur.com/ja-chcialem-dobrze-ale-on-jest-wredny/

I teraz w czym to dokładnie przeszkadza. Ano w tym , że jak coś mi nie wychodzi to mogę mało obiektywnie stwierdzić "nie działa...no dobra...czego to może być wina... no nie moja oczywiście gdyż nazywam się principal architekt 33 i 1/3... no to chyba.... wiem... to chujowy język". Nie można też oczywiście przechodzić ze skrajności w skrajność bo są naprawdę słabe narzędzia i podejścia ale wspomnianego efektu świadomym być trzeba by mniej subiektywnie stwierdzać tę słabość.

ogólnie tematyka warta osobnego wpisu a opisane bardziej tutaj : https://pl.wikipedia.org/wiki/Teoria_atrybucji. I to tez jest fajne : https://pl.wikipedia.org/wiki/Podstawowy_b%C5%82%C4%85d_atrybucji

W trakcie code retreat między innymi po to się paruje z innymi i kasuje kod by ludzie mogli na luzie pogadać o tym co można zrobić lepiej bo i tak nie będzie dowodów :)

Przeszkoda w samokształceniu numer 2

Efekt Krugera-Dunninga - czyli w sumie dosyć logiczna dytuacja, kiedy nasza wiedza jest na tyle mała, że nie jesteśmy w stanie obiektywnie ocenić, że jest mała i wydaje nam się, że jest duża. Też to w sumie opisałem kiedyś : http://pawelwlodarski.blogspot.com/2013/02/efekt-krugera-dunninga.html bo sam to u siebie zauważyłem.

I to działa tak, że np. Mam grupę znajomych i jak się z nimi spotykam to opowiadam dowcipy i częstą się śmieją (opisuję tutaj sytuację hipotetyczną bo z moich nadal nikt się nie smieje). Myślę - "jestem niezłym kawalarzem". Problem polega na tym, że pomiar został wykonany na wąskiej i dosyć mało reprezentatywnej grupie. Teraz analogia do IT. Np. pisze sobie aplikację i tworzę taki nieczytelny zjebany kod ale dookoła mnie wszyscy tworzą Managery i Helpery a mój kod w sumie najmniej się wywala na produkcji no i do tego manager Franek zawsze mnie poklepuje po plecach za wzorowo wysprzątane biurko- no to chyba jestem jednym z "lepszych zawodników" tutaj "nie no po co mam się znowu uczyć podstaw jak jestem jednym z lepszych zawodników?"

To był chyba jeden ze słynniejszych eksperymentów gdzie wykazano, że jeśli mówisz o kimś "jaki on jest" czyli np. "inteligentny" to ten ktoś będzie wybierał sobie prostsze i łatwiejsze zadania by te ocenę potwierdzić. Masa jest przykładów na necie - można zerknąć także i tutaj --> http://www.focus.pl/czlowiek/azjatyckie-tygrysy-kontra-zachodnie-mieczaki-8208?strona=2

Moja skromna obserwacja - niekiedy przychodzą na code retreat jacyś seniorzy czy architekci i oni momentami bardziej kaleczą kod niż studenci. Sam się dzisiaj czegoś znowu nauczyłem gdy przypominałem sobie to ćwiczenie i w sumie zaraz będę kaleczył kod pastwiąc się nad kodem funkcyjnym.

Przeszkoda w samokształceniu numer 3

Tak zwana awersja do straty. Znowu temat rzeka : https://en.wikipedia.org/wiki/Loss_aversion ale generlanie co roku niektórzy naprawdę przeżywają szok w momencie gdy mają skasować kod. A Własnie o to chodzi, żeby totalnie poczuć, iż to jest nauka, wyluzować i próbować nowych rzeczy. Uważasz, ze masa małych klas to więcej szkody niż pożytku - ok ale poeksperymentuj i nie stresuj się bo kod i tak zaraz zniknie. W sumie to taki snap chat tylko, ze zamiast pokazywać cycki pokazujesz na chwile kod, którego nie jesteś pewien. Reeeeelaks - zaraz zniknie.

Kod

Starczy tego nawijania. To będzie moja 4 edycja gdy prowadzę i 5 coderetreat gdy uczestniczę i naprawdę co roku powtarza się kilka opinii, ze "ło jezu znowu to samo ćwiczenie" a co roku KAŻDEN się uczy na tym bez WYJONTKU. Znudziła ci się Java? We no popróbuj czegoś innego. Za kilka akapitów zamierzam wystawić się na pośmiewisko próbując implementacji w Haskellu. Ale jednocześnie pokaże, jak to ćwiczenie pomoże mi lepiej zrozumieć Javę 8 i Scalę (ba nawet uświadomi mi pewne obszary ze skali, których jeszcze nie opanowałem).

A tutaj rożne sposoby podejść z poprzednich lat :

Łobiektuwka

Zazwyczaj ludzie zaczynają implementacje od tablicy booleanów i arrow coda z tysiącem ifów. Później gdy TDD wkracza do gry i kilku złych nawyków się zabrania - wtedy pojawia się jakaś implementacja w postaci klas czy typów i pierwsze zarysy modularyzacji. No i teraz popróbujmy tego cyklu Kolba czyli trochę refleksji.

Np. obiektowo to może być tak, że faktycznie "zenkapsulujemy" jakiś mutowalny stan :

object RealOOPCells {
  abstract class Cell{
    def evolve(neighbours:Int):Unit
  }

  class LiveCell(private var state:Boolean) extends Cell{
    override def evolve(neighbours:Int): Unit= neighbours match {
      case 2 | 3 => state=true
      case _ => state = false
    }

    override def toString="LiveCell"
  }

  class DeadCell(private var state:Boolean) extends Cell {
    override def evolve(neighbours: Int): Unit = neighbours match {
      case 3 => state=true
      case _ => state=false
    }

    override def toString="DeadCell"
  }
}

Teoretycznie jest dobrze ale własnie teraz przeprowadźmy obserwację. W sumie stan komórki jest tylko częścią równania gdyż równie istotni są jej sasiedzi, którzy już z punktu widzenia komórki "zenkapsulowani" nie są. W toku ćwiczeń i pisania testów w TDD często "samoz siebie" wychodzi, że tak naprawdę nie tyle jest ważny stan ile sam fakt czy komórka jest żywa czy nie. I wteyd można tak (bacznie obserwujcie jak zniknął typ Unit z deklaracji metod - czy to jest łatwiejsze do testów):

 abstract class Cell{
    def evolve(neighbours:Int):Cell
  }

  object LiveCell extends Cell{
    override def evolve(neighbours:Int): Cell = neighbours match {
      case 2 | 3 => LiveCell
      case _ => DeadCell
    }

    override def toString="LiveCell"
  }

  object DeadCell extends Cell {
    override def evolve(neighbours: Int): Cell = neighbours match {
      case 3 => LiveCell
      case _ => DeadCell
    }

    override def toString="DeadCell"
  }

teraz teoretycznie mamy dwa singletony - i mimo panującej nienawiści skierowanej na to słowo - tutaj to akurat dobrze. Bo gdy ludzie dochodzą do tego lub podobnego rozwiązania to nawet nie są świadomi, ze oto wyszli z mainstreamowego nurtu wzorców i zaimplementowali coś co się zwie się Flyweight_pattern czyli waga piórkowa. To po obiektowemu bo po kolejny momencie "AHA" okazuje się, że możemy iść z wytłumaczeniem w tę stronę --> Algebraic_data_type

No i dalej jakoś separujemy informacje o budowie planszy poprzez abstrakcyjne rozumienie współrzędnych :

trait Coordinates{
    def neighbours:Seq[Coordinates]
  }
  case class FlatCoordinates(x:Int,y:Int) extends Coordinates {
    override def neighbours: Seq[Coordinates] = for {
      xe <- (x-1 to x+1) if xe>=0
      ye <- (y-1 to y+1) if (ye >=0 && (xe,ye) != (x,y))
    } yield new FlatCoordinates(xe,ye)

    override def toString=s"($x,$y)"
  }

Fajne jest też to, że mając w Scali REPLa można sobie porobić "Eksperyment Driven Development" (bo musi być jakieś driven development) i posprawdzać czy działa

import LifeLibrary._
LiveCell.evolve(1)
LiveCell.evolve(2)
LiveCell.evolve(3)
DeadCell.evolve(1)
DeadCell.evolve(2)
DeadCell.evolve(3)
/*
res0: LifeLibrary.Cell = DeadCell
res1: LifeLibrary.Cell = LiveCell
res2: LifeLibrary.Cell = LiveCell
res3: LifeLibrary.Cell = DeadCell
res4: LifeLibrary.Cell = DeadCell
res5: LifeLibrary.Cell = LiveCell
 */

new FlatCoordinates(0,0).neighbours
new FlatCoordinates(2,2).neighbours
//res6: Seq[LifeLibrary.Coordinates] = Vector((0,1), (1,0), (1,1))
//res7: Seq[LifeLibrary.Coordinates] = Vector((1,1), (1,2), (1,3), (2,1), (2,3), (3,1), (3,2), (3,3))

No to było trochę gadania o tym by się nie bać eksperymentować to zobaczmy czy można jednocześnie wprowadzić odpowiednią abstrakcje planszy i wykorzystać tak czy inaczej dziedziczoną metode "toString"

 trait Board{
    def liveNeighbourhood:Iterable[Coordinates]
    def cellAt(c:Coordinates):Cell
  }

  class FlatBoard(board:Map[Coordinates,Cell]) extends Board{
    val newLine=System.getProperty("line.separator")

    override def liveNeighbourhood: Iterable[Coordinates] =
      board.keySet ++ board.keySet.flatMap(_.neighbours)

    override def toString: String ={
      val cells=for {
        y<- (0 to 10)
        x<- (0 to 10)
      } yield (x,y,board.getOrElse(new FlatCoordinates(x,y),DeadCell))

      cells.map{
        case (10,y,LiveCell) => "O"+newLine
        case (x,y,LiveCell) => "O"
        case (10,y,DeadCell) => "X"+newLine
        case (x,y,DeadCell) => "X"
      }.mkString(",")
    }
    override def cellAt(c: Coordinates): Cell = board.getOrElse(c,DeadCell)
  }

Wygląda dziwnie, trochę strasznie i niepokojąco. No dobra to było tak w kwestii przypomnienia, a teraz...

Czas an rzeźnię

Research results from the psychology of
programming indicate that expertise in programming is far more strongly related to
the number of different programming styles understood by an individual than it is the
number of years of experience in programming

Code retreat to także szansa na naukę innego języka gdy obok ciebie usiądzie ktoś kto np. programuje w czymś czego na oczy nie widziałeś/widziałaś. Cytat powyższy (ten w szarej ramce) jest podobno autorstwa tego zioma Tim Budd, który jest jakimś tam badaczem IT. I pomimo, że nie chce mi się tego wersyfikować to cytat brzmi fajnie. A do tego np. koncept, który był w Effective Java odnośnie używania niemutowalnych obiektów w sumie lepiej można zrozumieć jak się trochę takiej niemutowalnej codzienności z FP uszczknie w bardziej funkcyjnych językach.

Teraz eksperyment będzie polegał na implementacji choćby kawałka game of life w Haskellu, który jako język czysto funkcyjny nie pozwala na oszukiwanie. I później przeniesienie tego podejścia na Jave8 i Scalę by nauczyć się programowania czysto funkcyjnego w tychże językach. Początek jest obiecujący bo typ Komórki tworzymy po prostu tak :

data Cell = LiveCell | DeadCell deriving (Show,Eq)

Pierwsza rzecz, to nie ma tutaj żadnych powiązań z pojęciem klasy jaką znamy z Javy bo Typ to Typ. A druga kwestia to to "deriving". Tutaj własnie używamy sławnych (bądź niesławnych) "Klas Typów" czyli mówimy, że nasz Typ będzie "porównywalny" i "wyświetlany".

Teraz nasze wymagania co do evolucji komórki

evolveLive 2 = LiveCell
evolveLive 3 = LiveCell
evolveLive _ = DeadCell

evolveDead 3 = LiveCell
evolveDead _ = DeadCell

Apropo tego przykładu można w koncu zczaić dlaczego Pattern Matching to "mechanizm funkcyjny". W scali to za bardzo przypominało rozbudowany "instanceof". a tutaj - przypomnijcie sobie, ze szkoły przypadki gdy definiuje się funkcje, która dla "0 ma taką wartość a dla x!=0 inną - jest to pattern matching na intach na tablicy kredą!!"

I teraz to będzie ciekawe by obadać jak wyjdzie implementacja tego samego w innych językach, najpierw scala

sealed trait Cell
object LiveCell extends Cell
object DeadCell extends Cell

val evolveLive:Int => Cell = n =>  n match{
  case 2|3 => LiveCell
  case _ => DeadCell
}

val evolveDead:Int => Cell = n =>  n match{
  case 3 => LiveCell
  case _ => DeadCell
}

Mamy mechanizm zapewniania typu zamkniętego przy pomocy słówka "sealed" oraz wbudowany pattern matching. A teraz dla porównania Java8.

enum Cell {LiveCell,DeadCell}

Function<Integer,Cell> evolveLive=n-> {
            switch (n) {
                case 2:
                case 3:
                    return LiveCell;
                default:
                    return DeadCell;
            }
        };

        Function<Integer,Cell> evolveDead=n-> {
            switch (n) {
                case 3:
                    return LiveCell;
                default:
                    return DeadCell;
            }
        };

No nie wiem, może robię coś źle że takie rozbudowane konstrukcje mi wychodzą.

Dalej jakiś currying

evolve :: Cell -> Int -> Cell
evolve LiveCell = evolveLive
evolve DeadCell = evolveDead

Takie coś powinno nam pozwolić mapować później sekwencyjnie komórki w jakiejś kolekcji. Jednocześnie znowu czas na refleksję - w obiektówce i tak pierwszym parametrem byłoby this z obiektu i jakieś dynamic dispatch. Tutaj niezależność tej funkcji sprawia, że moglibyśmy ją sobie przekazywać,dekorować, liftować i masa innych ciekawych patentów. I reszta :

Scala:
val evolve:Cell=>Int=>Cell= _ match {
  case LiveCell => evolveLive
  case DeadCell => evolveDead
}
Java:
  Function<Cell,Function<Integer,Cell>> evolve=c->{
            switch(c){
                case LiveCell: return evolveLive;
                default: return evolveDead;
            }
        };

W Javie w switchu ostatnie musi być default bo inaczej kompilator nie przepuszcza. I jeszcze trochę eksperymwntów w Replu Haskella

*GameLive> evolveLive 2
LiveCell
*GameLive> evolveLive 7
DeadCell
*GameLive> evolveDead 3
LiveCell
*GameLive> evolveDead 1
DeadCell

///evolution
*GameLive> :t evolve LiveCell 
evolve LiveCell :: Int -> Cell


*GameLive>  evolve LiveCell 2
LiveCell
*GameLive>  evolve LiveCell 4
DeadCell

Abstrakcja poprzez aliasy

Aby operować słowami domenowymi możemy zamiast tworzenia nowych klas (bo tutaj chyba jako takich klas obiektowych nie ma) stworzyć aliasy typów.

type Coordinates = (Int,Int)
type Board=Map Coordinates Cell

board :: Map Coordinates Cell
board  = Map.fromList [
  ((0,0), LiveCell),
  ((1,0), LiveCell),
  ((2,0), LiveCell)]

Co ciekawe po przeklejeniu tego kodu do Scali trzeba było tylko lekko poprawić definicje mapy i wsio się kompiluje.

type Coordinates = (Int,Int)
type Board=Map[Coordinates,Cell]

val board  = Map[Coordinates,Cell](
              ((0,0), LiveCell),
              ((1,0), LiveCell),
              ((2,0), LiveCell)
            )

Przygotowania do nowej rundy

neighbours (x,y) =[(xe,ye)|xe<-[x-1..x+1], ye<-[y-1..y+1] , ye>=0 ,xe>=0,(xe,ye)/=(x,y)]

liveCoordinates = Map.keys
coordinatesToEvolve liveCoordinates =  liveCoordinates >>= \c -> neighbours c

cellsToEvolve :: Board -> [Coordinates]
cellsToEvolve=Data.List.nub . coordinatesToEvolve . liveCoordinates

Ten kod dla javowca zapewne będzie szokiem ale generalnie nie ma tam nic odkrywczego. "neighbours (x,y)" to nic innego jak iloczyn kartezjański z wyłączeniem środka czyli wszyscy sąsiedzi 2D. Zaś następny kawałek kodu może być ciekawy "liveCoordinates >>= \c -> neighbours c" tak więc ">>=" to flat map a "\c-> neighbours c" to lambda. I w sumie jak na to teraz patrze to ta lambda jest tam nadmiarowa

coordinatesToEvolve liveCoordinates =  liveCoordinates >>= neighbours

Niestety dalej nie umiem tego uprościć a pewnie można. Teraz nauczymy się czegoś w Scali gdy nie ma do dyspozycji całej tej obiektówki

val neighbours:Coordinates=>Seq[Coordinates]= c=> {
  val (x,y)=c
  for {
    xe <- x-1 to x+1
    ye <- y-1 to y+1 if (xe>=0 && ye >=0 && (xe,ye)!=(x,y))
  } yield (xe,ye)
}

val liveCoordinates: (Board) => Seq[Coordinates] =(b:Board) => b.keys.toSeq
val coordinatesToEvolve=(liveCoordinates:Seq[Coordinates])=> liveCoordinates flatMap neighbours

I teraz zaczynają się dziać ciekawe rzeczy bo przez to, że nie widzę, żadnej "standalone" funkcji, która zostawi mi unikalne elementy w kolekcji to muszę kombinować. To jest ten moment gdzie ze względu na czysto funkcyjny charakter ćwiczenia obiektówka w scali nie pomaga bo definiuje metodę "distinct" na instancji.

I mogę to rozwiązać definiując swoją własną funkcję :

val unique=(c:Seq[Coordinates])=>c.distinct
val cellsToEvolve=
  unique compose coordinatesToEvolve compose liveCoordinates

Trochę to marnotrawstwo więc może jakaś metoda generyczna , ETA ekspansion i coś w ten deseń.

def uniqueGeneric[A](c:Seq[A])=c.distinct
val cellsToEvolve2:Board=>Seq[Coordinates]=
  (uniqueGeneric (_:Seq[Coordinates])) compose coordinatesToEvolve compose liveCoordinates

Ewentualnie można coś pokombinować z lambdami

val cellsToEvolve3:Board=>Seq[Coordinates]=
  ((_:Seq[Coordinates]).distinct) compose coordinatesToEvolve compose liveCoordinates

val cellsToEvolve4:Board=>Seq[Coordinates]=
  ((l:Seq[Coordinates])=> l.distinct) compose coordinatesToEvolve compose liveCoordinates

Jak widać rozwiązań jest kilka i każde ma w sobie coś przerażającego. Co do rzeczy przerażających pozostało nam jeszcze :

class Coordinates{
    public final Integer x;
    public final Integer y;

    Coordinates(Integer x, Integer y) {
        this.y = y;
        this.x = x;
    }

    @Override
    public String toString() {
        return "("+x+","+y+")";
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Coordinates that = (Coordinates) o;

        if (x != null ? !x.equals(that.x) : that.x != null) return false;
        return !(y != null ? !y.equals(that.y) : that.y != null);

    }

    @Override
    public int hashCode() {
        int result = x != null ? x.hashCode() : 0;
        result = 31 * result + (y != null ? y.hashCode() : 0);
        return result;
    }
}

aaa pola publiczne, wszyscy zginiemy!!! I jeszcze znajdowanie sąsiadów

public static Collection<Coordinates> neighbours(Coordinates c){
        return IntStream
                .rangeClosed(c.x - 1, c.x + 1)
                .boxed()
                .flatMap(x ->
                                IntStream.rangeClosed(c.y - 1, c.y + 1)
                                        .boxed()
                                        .map(y -> new Coordinates(x, y))

                ).filter(coord -> coord.x >= 0 && coord.y >= 0 && !c.equals(coord))
                .collect(Collectors.toList());
    }

Być może na zwykłych forach po bożemu byłoby to bardziej intuicyjne ale ćwiczenie polega - przypomnijmy - na implementacji jak najbardziej czysto funkcyjnej. I dla porównania kod powyżej robi dokładnie to samo co ta linijka w Haskellu :

neighbours (x,y) =[(xe,ye)|xe<-[x-1..x+1], ye<-[y-1..y+1] , ye>=0 ,xe>=0,(xe,ye)/=(x,y)]

Na podstawie tego eksperymentu nie polecałbym Haskella do organizacji gdzie nagradza się za ilość napisanych linii kodu (oni to chyba nazywają Performance Indikator czy coś takiego, taki koncepcik zabrany z już pożółkłych książek epoki industrialnej) natomiast polecałbym do organizacji gdzie każda zbędne linia kodu kosztuje.

I obiecana rzeźnia na koniec

howManyLiveNeighbours :: Board -> Coordinates -> Int
howManyLiveNeighbours b c =
  let neighbourCells=fmap (`Map.lookup` b) (neighbours c) in
  length $ filter (/= Nothing) neighbourCells

--nextTurn :: Board -> Board
--nextTurn =  cellsToEvolve

getCells board=fmap (Data.Maybe.fromMaybe DeadCell)  $ fmap (`Map.lookup`  board)  $  cellsToEvolve board

toLiveNeighbours=fmap (howManyLiveNeighbours  board) . cellsToEvolve

finalSequence=cellsToEvolve board  `zip` (getCells board `zip` toLiveNeighbours board)

Generalnie ten kod jest mieszanką mojej nieudolności w Haskellu i podpowiedzi z pluginu "Stylish Haskell", który robił co było w jego mocy. Dodatkowo nauczyłem się pisać komentarze w Haskellu.

Nauka

Pomimo, że ostatni wklejony akapit kodu jest czystym złem to jego celem nie jest ładnie wyglądać a stanowić szczebel w nauce. Na przykład jak idziesz pierwszy raz na siłkę to raz, że używasz małych ciężarów a dwa to ćwiczysz źle - po prostu źle. Każdy kto ćwiczy kiedyś zaczynał na siłce i 90% procent ludzi to rozumie i pomaga nowym (te 10% to ludzie którzy wstrzyknęli nie w tę żyłę oraz top managerowie, którzy muszą być top performent szczególnie na tle innych). Na szczęście jeszcze nie ma kursów "zarządzanie swoim wizerunkiem na siłowni" i nie trzeba udawać idealności w każdej pozycji z ciężarem w ręku. Podobnie Ty będziesz mógł nie przejmować się bzdurami new age w trakcie ćwiczeń bo kasujesz swój kod a z tego kodu co tutaj wkleiłem w tym artykule ludzie będą się jeszcze długo śmiali (przy optymistycznym założeniu, że ktoś to wszystko przeczyta)

niedziela, 11 października 2015

Synteza - od hejtu do oświecenia

"It is necessary that at least once in your life you doubt, as far as possible, all things."
René Descartes

Flejmy są tak samo fajne jak urywki z reality show gdzie ludzie przerzucają się bluzgami. Niestety czasem uczestnicy takiego dyskursu udają sztucznie, ze są na tzw "poziomie" przez co całe widowisko traci na rozrywkowości ale mimo wszystko zazwyczaj kilka postów jest wesołych.

Tematyka flejmu jest bardzo zależna od dziedziny w jakiej pracujemy. Zapewne gdzieś tam jest nisza w której grupa ludzi prawie się zabije w trakcie dyskusji czy lepsze jest norma ISO-88976 czy ISO-88975. Sam kiedyś zmarnowałem kawałek życia na dyskusje o roli UML w pracy programisty - a życie to tak jak zęby mleczne - ma się je raz w życiu.

Z innych ciekawych flejmów jakie pamietam to

  • C++ vs Assembler - to jeszcze za czasów kawiarenek internetowych ( "a co to kawiarenka internetowa?")
  • Java vs C#
  • PHP vs cokolwiek

Ale co było takim pierwszym dużym flejmem na gruncie rzeczy mocno abstrakcyjnych?

  • Windows vs linux ?? wcześniej ...
  • yyy OOP vs Proceduralne ?? wcześniej ...
  • mainframe vs PC ?? wcześniej..
  • Lampy vs scalaki ?? wcześniej
wcześniej, dużo wcześniej!!!

Epistemologia

Epoka tak zwanego renesansu to wiek gdy pewna grupa ludzi pozbawiona innych rozterek życiowych skupia się na rozwiązaniu problemów pierwszego świata czyli np. "Czy mogę udowodnić, że świat w którym żyję naprawdę istnieje?" I takie tam.

Utworzyli oni dwie frakcje - Empiryści - którzy uważają, że wiedzę nabywa się zmysłami i Racjonaliści żywiący przekonanie, że wiedza jako taka istnieje w nas od urodzenia i musimy ją niejako "odzapomnieć" poprzez procesy myślowe i dumanie.

  • Empiryzm - Locke, Berkeley, and Hume
  • Nie ma wiedzy jako takiej a tylko to co odbieramy przez zmysły - jeśli urodziłeś się ślepy to skąd "wiesz", że "niebieski" to niebieski?
  • Czy tez pojecie próżni - w pewnym momencie historii zupełnie absurdalne koncepcyjne - później potwierdzone obserwacjami. (Można to nawet podciągnąć pod czasy niemal współczesne pod hasłem "Bóg nie gra w kości" - kto ciekawy niech sobie pogogluje)
  • Realizm- Descartes, Leibniz
  • Czy musimy wpierw zaobserwować wzór na pole koła aby ten wzór zaczął obowiązywać? (wskazówka - nie, nie musimy)
  • Jak "wygląda,pachnie,brzmi" etyczne zachowanie,dobro,zło ?
  • czy możemy przewidzieć rzeczy, których jeszcze nie widzieliśmy?

Posłużymy się fejsbukiem dla zmarłych ludzi czyli wikipedią. Pierwszy pojawia się Francis Bacon, który tworzy coś co będzie totalnie olewane przy dyskusjach o metodykach IT kilkaset lat później - "metodę naukową". Twierdzi, że postęp technologiczny najbardziej hamują różnego rodzaju zabobony i "sub optymalne konfiguracje społeczne" - dzieli je na :

  1. “idols of the mind.”
  2. “idols of the tribe”
  3. “idols of the cave”
  4. “idols of the marketplace”
To ostatnie w IT ma się świetnie. Ostatnio był fajny cytat na twiterze, że "teraz jest więcej firm, które specjalizują się w szkoleniach ze scruma niż firm które faktycznie używają scruma."

Teraz na scenę wchodzi Rene Descartes na polskie tłumaczony "Kartezjusz". To tak jak np. Kowalski tłumaczy się na angielski "Smith". To jest koleś od cytatu I think therefore I'am , który podobno w oryginale brzmi jednak inaczej.

On generalnie poleca coś innego niż wykorzystywanie zmysłów gdyż ktoś lub coś może jego zmysłami manipulować. Poleca poddać wszystko co się wie w wątpliwość i wyprowadzić wiedzę od nowa. Wszystko to jest nieco zakręcone ale na szczęście istnieje poniższy filmik

John Locke jeszcze zanim wystąpił w LOSTach, żywił przekonanie, że ludzki po urodzeniu to "tabula rasa" czyli tzw. "Czysta Karta". Miało to dosyć poważne implikacje dla ówczesnego światopoglądu, że jedni ludzi są lepsi od drugich "by default" i dlatego ci pierwsi się opierdalaja a ci drudzy zapierdalają. Później będzie to stanowić cegiełkę w krwawych przewrotach rzeczywistości gdy ogramna masa ludzi, której źle się dzieje zacznie się dopytywać "zaraz zaraz ale kto własciwie ustala te zasady" - ale to dopiero później

Nic też dziwnego, że Locke angażował się w politykę i kilka fajnych rzeczy "pomógł osiągnąć" - https://en.wikipedia.org/wiki/Natural_and_legal_rights#John_Locke

Gottfried Wilhelm Leibniz - od razu ciekawa rzecz, to on stworzył słowo Monada. I wtedy podobno też mało kto to rozumiał :

"Each monad is isolated from other
monads, and each contains a
complete representation of the
whole universe in its past,
present, and future states."
Na zadnym zdjęciu jednak nie nosi koszulki z niezwykle niszowym żartem o Kategorii Endofuntorów.

George Berkeley lub po prostu "Dżordż" - twórca Idealizmu (?). Szerzył ekstremalny empiryzm twierdząc, ze tylko rzeczy, które odebraliśmy zmysłami w ogóle istnieją, że łamiące drzewo w lesie nie wydaje dźwięku jak nikt nie może go usłyszeć. Zresztą drzewo też nie istnieje - jak i łyżka. Wydaje mi się, że do tego zwykłe zioło nie wystarczy i musiał jeździć do Amsterdamu jeszcze jak grzybki były tam legalne.

I w końcu David Hume, który (też grał w LOSTach tego kolesia, który siedział w bunkrze) nie tyle optował za tym, że świat należy badać poprzez zmysły ale także, iż i tak większość ludzi to robi i to tak trochę niepoprawnie tworząc nawyki - np. przez ostatni rok słońce wschodziło każdego dnia to i jutro wstanie (wiem chociaż nie mam dowodu). Jeśli przypatrzeć się bliżej filozofii Huma to w sumie przewidział, że niektórzy ludzie będą pracowac w korpo kilkadziesiąt lat codziennie robiąc to samo - w każdym razie ...



user : jhnlocke27 is online
user : DES~TES is online
user : HumE is online
user : lbnITZ is online
user : BEKElej is online

jhnlocke27 : Widzę, że wszyscy są
lbnITZ : no było wiadomo
DES~TES : chyba,że to boty. Nie jestem pewien
BEKElej : zalogowaliście się bo ja się zalogowałem
BEKElej : pewnie was tu nie było zanim nie przyszedłem. Tego czata nie było
BEKElej : Czy program rzuca wyjątki jeśli nikt nie obserwuje konsoli?
user : HumE : o widzę, że było jarane :)?
                 |                     
                 |.|                    
                 |.|                    
                |\./|                   
                |\./|                   
.               |\./|               .   
 \^.\          |\\.//|          /.^/    
  \--.|\       |\\.//|       /|.--/     
    \--.| \    |\\.//|    / |.--/       
     \---.|\    |\./|    /|.---/        
        \--.|\  |\./|  /|.--/           
           \ .\  |.|  /. /              
 _ -_^_^_^_-  \ \\ // /  -_^_^_^_- _    
   - -/_/_/- ^ ^  |  ^ ^ -\_\_\- -  
lbnITZ :
user : HumE : Ty tak z przyzwyczajenia?
lbnITZ : nie masz argumentów to trolujesz
jhnlocke27 : Co to jest trol?
DES~TES : twój stary
jhnlocke27 : poważnie pytam?
DES~TES : twój stary
lbnITZ : JEST TU JAKIŚ ADMIN?
user : K.A.N.T is online
user : jhnlocke27 is offline
user : DES~TES is offline
user : HumE is offline
user : lbnITZ is offline
user : BEKElej is offline

"Critique of Pure Reason"

To co próbuje zrobić Kant to synteza (chociaż wtedy jeszcze nie można mówić o syntezie) obydwóch postaw : Empiryzmu i Racjonalizmu - tak aby uzyskać jeden spójny bogatszy model (choć jeszcze wtedy nie można mówić o modelach). I tutaj można chyba wziąść wziąć slajd ze standardowej prezentacji managerskiej numer 5 i użyć słowa Synergia!!

Wiedza to kombinacja faktów obiektywnych - których jako ludzie nie jesteśmy w całości w stanie pojąć - oraz subiektywnych ograniczonych obserwacji zjawisk. Obserwuję spadające jabłko czego doświadczam zmysłami ale aby zdefiniować "obserwuję" muszę założyć, że istnieje pojęcie "obserwacji" i "przestrzeni" i one musza być zdefiniowane przed obserwacją samą w sobie. (Tutaj upraszczam po swojemu)

Dwa filmiki - pierwszy Śmieszny:
Drugi dłuższy :

W sumie to o czym do tej pory pisaliśmy to był taki wstęp do głównej myśli artykułu. Pozostając wciąż w klimatach filozoficznych - pytanie czy można proces, który zaobserwowaliśmy - czyli spajanie dwóch przeciwstawnych idei uogólnić tak by opisać go poziom abstrakcji wyżej.

Tak, tutaj właśnie pojawia się synteza...

Synteza

The learner always begins by finding fault, but the scholar sees the positive merit in everything.
Georg Wilhelm Friedrich Hegel

To co Kant zrobił dla przypadku szczególnego - Hegel opisał, uogólnił i zdaje się, że to doprowadziło do powstania całej dziedziny zwanej Dialektyką

Z opisu filozofii Hegla wynika trochę, że miał on tak pokręcone teksty, że nawet nie można śmiesznego filmiku na ten temat na youtube znaleźć. Dlatego poniższy film nie jest smieszny ale wciąż przydatny. Warto z niego zapamiętać cytat : "Learn from Ideas you dislike"

Nauka pod innym kontem

  1. Rozumiem pewną ideę. Mogę bronić tej idei wzmacniajac swoje przekonanie odnośnie tejże idei.
  2. Odnaleźć przeciwstawna ideę. Zrozumeić przeciwstawną ideę.
  3. Obydwa podejścia są osobnymi perspektywami bardziej abstrakcyjnej idei.
  4. Uzupełniając luki w bardziej abstrakcyjnej idei ogarniam potęzniejszy model (chociaz wtedy o modelu nie mozna było mówić - dop. autora).

Technologia - powrót do codzienności

No dobra ale co takiego ten wywód daje nam w świecie cyfrowych cudów? Jak już jesteśmy przy filozoficznie brzmiących słówkach to przyjrzyjmy się Polimorfizmowi . Od wczesnych czytanek obiektowych młodzi adepci sztuki programowania uczą się prostych przykładów gdzie Piesek i Kotek dziedziczy ze Zwierzątka a cały koncept nazywa się Polimorfizm gdzie słówko wymyślili Grecy jeszcze zanim zaczęli wydawać więcej niż zarabiali.

To czego nie napisali lub napisali a każdy podświadomie olał bo przecież "polimorfizm to dziedziczenie" to taka kwestia, że jest kilka rodzajów polimorfizmu a dziedziczenie to tzw. "sybtype polimorphism", który jest jedynie podzbiorem koncepcji polimorfizmu jako takiej --> więcej tutaj https://en.wikipedia.org/wiki/Polymorphism_(computer_science)

Akurat to mi zapadło w pamięć bo widziałem prezentację Rubiego gdzie ktoś się pytał
"czy jest zaimplementowany polimorfizm?
-doprecyzuj proszę
-no czy można dziedziczyć..."

Idąc dalej tym tropem w innym typie polimorfizmu - Ad Hoc Polimorphism natrafimy na koncepcję "Type Classes". Jest to coś czego w świecie Javy nie doświadczyłem a jednocześnie jest trudno do ogarnięcia dla Javowca bo ma w sobie słowo "classes" - a nie o takie klasy chodzi.

I na przykład w Scali mamy coś takiego :

List(1,3,2).sorted
//res1: List[Int] = List(1, 2, 3)
Jak zerkniemy w definicję metody sorted - wtedy zobaczymy, że przyjmuje ona jeszcze dodatkowy parametr (osławiony implicit), który określa pojęcie "Uporządkowania" dla danego typu
 def sorted[B >: A](implicit ord: Ordering[B]): Repr = {

Nie do końca byłem w stanie to zrozumieć jako mechanizm programowania funkcyjnego bo to wygląda jak standardowa strategia z Javy (jeśli odjąć to implicit). Tak jak przekazywanie komparatora do metody sort za starych dobrych czasów.To był świat OOP gdzie wszystko jest obiektem, jeden sztywny punkt widzenia. Rozwiazanie przyniosło dopiero spojrzenie z innego usztywnionego punktu widzenia FP - http://learnyouahaskell.com/making-our-own-types-and-typeclasses

Jeśli mam tak zdefiniowane dane
data DataType = Small | Medium | Big
I wywołam :
*Main> Small < Medium

<interactive>:3:7:
    No instance for (Ord TrafficLight) arising from a use of ‘<’
    In the expression: Small < Medium
    In an equation for ‘it’: it = Small < Medium
Ten bidny komputer nie wie jak to nawet do siebie porównać Ale "wzbogaćmy typ"
data DataType = Small | Medium | Big deriving (Eq,Ord) 
i Już śmiga!
*Main> Small < Medium
True
*Main> Small == Medium
False

Spojrzenie z Haskella uwalnia od narzutu powiązanego z czystą mechaniką w Scali, która do opisu konceptu "Klasy Typu" używa "mechaniki klasy OOP". W skrócie pozbyliśmy się mechaniki klas by lepiej zrozumieć istotę "Klas"!

Kiedy nastąpi synteza?

[PRZERWA NA REKLAMĘ]
Zacząłem równoległy Nowy blog pisany złym angielskim :

[/PRZERWA NA REKLAMĘ]

Idąc za tokiem myślowym Hegla mieliśmy już za sobą ekstremum obiektowości, teraz czeka nas ekstremum programowania funkcyjnego co jasno odsłoni braki tego podejścia i wtedy nastąpi zbalansowanie i unifikacja obydwu podejść rodząc nową kategorie (czy coś w tym stylu)

Innym ciekawym podejściem - choć być może niewygodnym dla tych, którzy zarabiają rysując kwadraty - jest opcja, że FP jest już syntezą wszystkich dobrych praktyk, które pojawiły się na gruncie programowania obiektowego (teretycznie większość wzorców projektowych można wyrazic przy pomocy jednego konceptu - funkcji, a Effective Java to dobry wstępniak do FP dla Javowców)

Co by się nie stało to świat idzie dalej - Red Queen Race i te sprawy - być moze za kilka lat będziemy mieli takie technologie, że bardziej będzie się opłacało zapłacić kilku kumatym ludziom by stworzyli coś zupełnie nowego niż armii utrzymaniowej - być może model wartego górę złota programisty Cobola nie powtórzy się z obecną technologią.

I w tym kontekście nauki nowych rzeczy - z tego co widziałem nauka scali idzie najgorzej ... programistom Javy - a to na skutek pewnych luk pojęciowych.Powstał pewien tight coupling do ograniczonych mechanizmów zapewnionych przez jeden język zetknięcie ze scalą jest pierwszym momentem gdy stykają się z zupełnie nowymi pojęciami (jak np. wspomniane type classes (wole nie tłumaczyć by nie wyszło, że "die hard" to szklana pułapka). nawet ktoś kto nie tykał się JVM i pisałem w F# jest w stanie szybciej skumać poprawne sposoby wykorzystania mechanizmów scali aniżeli właśnie czyści tacy rasowi OOProgramiści Javy. Być może uogólnienie wynika z tego, że znam masę programistów Java a jedynie garstkę programistów F# ;) ale z drugiej strony akurat Java 7 nie byłam najlepszym narzędziem do nauki tego co przyszło w Javie 8 i być może Java8 nie jest najlepszym sposobem na naukę tego co ma przyjść w Javie9. W sumie to Java8 też nie jest najlepszym narzędzie do nauki tego co przyszło w ... Javie8:D

I na koniec obserwacja bardzo złego podejścia do nauki jako takiej - "maksymalizacja tylko i wyłącznie tego co robię tu i teraz". W ten sposób biega się w kółko po tym samym pudełku, próbuje rozwiazywać problemy w ten sam sposób szlifując trochę parametry i jednocześnie oczekuje coraz lepszych rezultatów...