niedziela, 16 listopada 2014

Po Code Retreat 2014

Gdy piszę te słowy przyszły już pierwsze opinie na googlowym formularzu. Ludziom się generalnie podobało a po obecności słów "ch*j" i "du*a" wnioskuję, że studentom także.

Za rok trzeba będzie mocniej się postarać by ludzie mieli więcej szans popełnić błędy w kodzie i się na nich uczyć zamiast rozkminiać samą grę nad kartką. Najlepiej od razu podpowiedzieć kilka niedoskonałych rozwiązań na standardowe pytania - jak reprezentować planszę i od czego zacząć pisać testy.

Przez ostatnie dwa miesiące słyszałem wiele głosów w stylu "ej no znowu ta gra życie, może coś nowego..." a koniec końców chyba nie licząc jednej lub dwóch osób wszyscy uczestniczyli po raz pierwszy i doskonale pojechaliśmy "klasyką".

  1. Sesja wstępna-dowolna
  2. TDD i tylko jeden poziom zagłębienia kodu w metodach
  3. Jeden poziom zagłębienia, metody maksymalnie 5 linii długości i brak else
  4. obiad(oj jedzenia sporo było)
  5. Sesja "cicha" oraz do wyboru albo object-calisthenics lub not only OOP - wszyscy wybrali to pierwsze ;)
  6. Zostawiamy kod z poprzedniej sesji i mamy dodatkowe wymagania

Dominowała Java ale pojawił się też .Net, Ruby i Scala. Im więcej języków tym ciekawiej.

Dziękuję wszystkim za przybycie i organizatorom za zadbanie o wszystkie detale spotkania.

Ściana w kodzie

podobny temat poruszyłem już tutaj : o instanceof

Ciekawy problem powstaje gdy rozwiazujemy zadanie tworząc abstrakcję "Cell" i klasy pochodne "LiveCell" oraz "DeadCell". Jednocześnie gdzieś w kodzie musimy zliczać ilość żywych komórek - jak to zrobić?. Cześć osób dodaje do Cell metodę "isAlive"

Główny problem z tym rozwiązaniem polega na tym, że jeśli wyobrazimy sobie diagram klas to generalnie ten kwadrat co jest na górze nie powinien nic wiedzieć o tych co są na dole (chyba, ze ktoś rysuje do góry nogami) bo jeśli wie to jest ryzyko, że jak się zmieni coś na dole to trzeba będzie zmieniać przez tę zależność dosłownie wszystko. A jeśli w kwadracie na górze mamy metodę "isAlive" to on już wie, że gdzieś tam dziedziczy z niego żywa komórka, a teoretycznie cały bajer polega na tym aby bezboleśnie dodawać nowe podtypy bez mieszania w tym co jest zrobione do tej pory. Jeśli nie tak to jak można inaczej?

Prześledźmy taki tok myślowy :

  • Jeśli ktoś reprezentuje komórki przy pomocy boolean na tablicy to wykorzysta wartość true/false do stwierdzenia czy komórka jest żywa
  • Podobnie jeśli reprezentujemy je przy pomocy 0 i 1 - tutaj też nie będzie problemu.
  • enum {DEAD,ALIVE} - również łatwo rozpoznać typ komórki
  • LiveCell, DeadCell - tutaj można postąpić analogicznie. Generalnie dziedziczenie pozwala nam przenieść część logiki z kodu do kompilatora, który to wybierze odpowiednia implementację przez co sam kod jest bardziej elastyczny. Ale to nie znaczy, że wszystko ma się sprowadzać do tylko i wyłącznie tego mechanizmu. Tutaj łatwo można sprawdzić typ komórki w jakimś niezależnym i łatwym do testowania komponencie przy pomocy budzącego emocje "instanceof". Jeśli używamy tego mechanizmu do sprawdzenia ile elementów danego typu jest kolekcji wtedy raczej jest to ok, błąd wystąpi wtedy gdy używamy go by na podstawie typu wykonać jakieś operacji dla żywej komórki(i co gorsze rzutowanie) - wtedy w zasadzie wracamy do bazowego "if (komorka zywa) evoluujTak else evoluujInaczej"

Email Calisthenics

W trakcie ostatecznej retrospektywy wpadliśmy również na pomysł nowego ćwiczenia - bardziej w kierunku tych ludzie co więcej "komunikują niż programują".

W trakcie "Global Day of Email Retreat" uczestnicy grają w "Grę w życie" i mają ją rozwiązać odpowiednim łańcuchem maili. Ograniczenia.

  1. Respect people's time - Max 3 Recipients per email
  2. Focus - Only one discussion thread per email
  3. Save our eyes - whole email is written with the same font
  4. Don't overreact - No Top Management in CC
  5. Merit Arguments - No personal attacks in an email
  6. Avoid templates - participants can not use words "ASAP,Unacceptable,Concerned,Urgent or Opportunity"
Czy coś w ten deseń...

wtorek, 11 listopada 2014

Nauka Scali i Javy 8

Zanim przejdziemy do pierwszego spotkania-warsztatów przeznaczonych nauce Scali tylko i wyłącznie - wpierw kącik społecznościowy

Kącik społecznościowy

  • W najbliższa sobotę CodeRetreat - zapisy pod linkiem po prawej ---> link po prawej(zapisy) . Niby tam lista jest zamknięta ale dodajcie się na "waitlistę" bo kilka osób jeszcze pewnie zrezygnuje. W tym roku zapraszaliśmy speców z zagranicy do poprowadzenia wydarzenia i chociaż ich nie będzie to przysłali wskazówki jak tam to wydarzenie lepiej poprowadzić
    • Start by asking people what they would like to learn
    • Write down what they say on a flipchart or post-its
    • Group them into categories, and write down the number of "votes" for each category
    • Usually, you get things such as: pair programming, something new, TDD, Refactoring
    • If there are first timers, start with the normal free session that helps familiarize with the problem. Otherwise you can skip it
    • Then pick the categories in the order of votes and select the constraints that answer to those categories. You might need to improvise or ask for help - Adi and I will be available to help
  • A 20 listopada na DMCS odbędzie się wykład Michał Balińskiego o Amazon AWS. Michał jest na tę chwilę chyba najlepszym specjalistą w tych tematach w Łodzi - także na tę chwilę zapisało się już 40 osób i wy zapiszcie się także w linku obok Link obok do zapisów. Może pobijemy rekord publiczności. Obecny chyba nalezy do Grześka Borkowskiego do którego swego czasu przyszło około 70 osób na prezentacje o Javascript
  • .

Pierwsze spotkanie Nauki Scali

Scali warto się uczyć i jestem w pełni świadom tego, że są ludzie którzy pałają do tego języka nienawiścią (sam kiedyś tam byłem) ale jak ktoś nie chce nie musi używać natomiast możliwości edukacyjne są moim zdaniem ogromne :

  • REPL i worksheet są cudowne jeśli chodzi o prezentację uczestnikom tego co dzieje w kodzie. Nie trzeba printlainami iterować po elementach listy bo jest ona niejako "prześwietlana" Jeśli jest coś takiego w Javie to ja nie znaju.
  • Sam zacząłem interesować się scalą i programowaniem pod kątem nauki programowania funkcyjnego zanim Java 8 była gotowa i jeśli chodzi o ten temat to jest on dużo przejrzystszy w scali : funkcja to funkcja bez względu na to ile ma parametrów - gdzie w javie to programowanie funkcyjne jest zrobione najbardziej obiektowo chyba jak można było : Supliery,Consumery, funkcje, bifunkcje (a w dodatku "Updadek Bastionu" będą TriFunkcje i nowe zbroje).
  • W tym języku są koncepcje jak choćby pattern matching, które jak się okazuje (przynajmniej dla mnie było to odkrycie) są także nie obce wielu innym językom. Zamiast czekać nim pojawią się w Javie można z nimi zapoznać się już teraz.
  • I ostatni - moim zdaniem najważniejszy strategicznie punkt - generując anomalię edukacyjną polegającą na wzajemnej nauce relatywnie zaawansowanej technologi bez - (uwaga to bardzo ważne) - bez jasnego zysku komercyjnego - można przerwać zjawisko takiej "łódzkiej samo nakręcającej się spirali gówna" (rysunek dzięki uprzejmości mojej poniżej)

Jeśli ludzie nie byli tylko zwyczajowo mili to ogólnie się podobało. Generalnie idea jest taka aby pojawiła się jakaś grupa ludzi chcąca prowadzić warsztaty i to całe przedsięwzięcie się skalowało - są już chętni także następnym razem poprowadzi ktoś inny itd.

Co należy umieć aby poprowadzić warsztat

Tutaj dam linka sam do siebie - Pewność siebie poprzez niewiedzę. Generalnie najlepszą odpowiedzią co trzeba zrobić aby poprowadzić warsztat ze scali - należy nie wiedzieć, że nie ma się odpowiedniej wiedzy aby go poprowadzić. A później trzeba się tylko zgłosić, opracować materiał i przećwiczyć warsztat aby był ciekawy dla uczestników - i to tylko tyle.

Jak widać sam wykorzystuje tę technikę generując w sobie urojenia, że moje działania jakoś wpłyną na to co się dzieje w tym mieście. Ale dopóki ja mam z tego zabawę a ludziom się podoba i się czegoś uczą to będziemy to robić.

Co więcej

TomTom zaczął się angażować w edukację studentów w Łodzi. Na pierwszych zajęciach mieli okazję poznać trochę Scali i Playframework.

A TERAZ KOD.

Pierwsze porównanie

Na samym początku szybkie porównywanie scali i javy8 pod kątek filtrowania elementów listy przy pomocy lambdy (nie wiem czy to fachowo powinno nazywać się "przetwarzanie batchowe","przetwarzanie strumieniowe"czy też "przetwarzanie deklaratywne"?)

val lista=List(1,2,3,4,5) 
lista.filter(element=>element>2)
zadanie : stworzyć listę elementów 'a','b','c','d','e' i odfiltrować tak by zostało 'd','e'
val lista2=List('a','b','c','d','e')
lista2.filter(e=>e>'c')

Java
List lista = asList(1,2,3,4,5);
// a to nie wystarczy
Stream filter = lista.stream().filter(element->element > 2);
//trzeba tak
List result = lista.stream().filter(element->element > 2).collect(Collectors.toList());

Tworzenie listy

Aby słuchacze się nie nudzili - kilka ciekawych sposobów na inicjację(tudzież tworzenie) listy

List.range(0, 10, 2)
List.fill(7)(42)
List.tabulate(10)(elem=>s"element ${elem}")
"tekst".toList
zadanie : stworzyć listę z kolejnymi kwadratami liczb od 0 do N=20 i zostawić tylko podzielne przez 3
val lista=List.tabulate(20)(e=>e*e)
lista.filter(_ % 3==0)

sortowanie i mapowanie

lista.filter(element=>element>50).sortWith((elem1,elem2)=>elem1>elem2).map(elem=>elem+1)
zadanie : stworzyć listę od 1 do 100, zostawić elementy większe od 50 i posortować tak by parzyste liczby były na początku listy
List.range(1,100).filter(e=>e>50).sortWith((e1,e2)=>e1%2==0)

Java
List result = lista.stream()
    .filter(e->e > 2)
    .sorted(Comparator.reverseOrder())
    .map(e->e+1)
    .collect(Collectors.toList());
  
  result.forEach(System.out::println);
Przy pierwszym zetknięciu się z uproszczeniem zapisu przy pomocy podkreślenia czytelnik może przeżyć szok lecz zauważenie zysków tego zapisu to tylko kwestia czasu i praktyki.
 lista.filter(e=>e>2).sortWith((e1,e2)=>e1>e2).map(e=>e+1)
//lub lepiej
        lista.filter(_>2).sortWith(_>_).map(_+1)

Podzbiory

 val lista=List(1,1,2,1,3,1,4,5) 

lista.takeWhile(_==1)                     //> res0: List[Int] = List(1, 1)
lista.dropWhile(_==1)                     //> res1: List[Int] = List(2, 1, 3, 1, 4, 5)
Stream stream = Stream.of(1,1,2,1,3,1,4,5);
  
  List result = stream
    .limit(5)
    .skip(2)
    .sorted(Comparator.reverseOrder())
    .map(e->e+1)
    .collect(Collectors.toList());
  
  result.forEach(System.out::println);
zadanie : stworzyć listę od 0 do 100 ,posortować po drugiej cyfrze (czyli 19 jest większe od 88) i zostawić początek listy mniejszy od 90.
List.range(0, 100).sortWith((e1,e2)=>(e1%10) > (e2%10)).takeWhile(_<90)

Kilka kolekcji i Flatten

Przy tym cwiczeniu dla zwiększenia satysfakcji uczestników można (a nawet należy) wspomnieć różnicę pomiędzy listą i wektorem.

 val lista=List("elektromagnetyczny","pierun","trzy")
     //Vektor                                             
 val samogloski=Vector('a','e','u','y')    //> samogloski  : scala.collection.immutable.Vector[Char] = Vector(a, e, u, y)
 //Array
 def zostawSamogloski(s:String)=s.toCharArray().filter(samogloski contains(_))
                                                  //> zostawSamogloski: (s: String)Array[Char]

 lista.map(zostawSamogloski)               //> res0: List[Array[Char]] = List(Array(e, e, a, e, y, y), Array(e, u), Array(y
                                                  //| ))
 lista.map(zostawSamogloski).flatten       //> res1: List[Char] = List(e, e, a, e, y, y, e, u, y)
 
 //lub
 lista.flatMap(zostawSamogloski)
zadanie : zdublować przy pomocy flatMap wszystkie elementy listy List(1,2,3) -> List(1,1,2,2,3,3)
List(1,2,3).flatMap(e=>List(e,e))

redukcja

Generalnie redukcję na siłce robi się na wiosnę aby na lato by kaloryfer - tutaj jednak chodzi o redukcję kolekcji do jednej wartości. W poniższym przykładzie będzie również zaprezentowany mały przykład pokazujący różnice pomiędzy metodą i funkcją

val lista=List(1,2,3,4,5)

//redukcja
lista.reduce(_*_) 

//ponowne wykorzystanie funkcji
def add(a:Int,b:Int)=a+b 

lista.reduce(add)  
lista.foldLeft(0)(add)

//ale mozna i
val addf=(a:Int,b:Int)=>a+b  

//jaka jest roznica? Metody moga mieć generyki i na tym etapie tyle tłumaczenia musi wystarczyć :)

val reduceText=(a:Int,b:Int)=>a+":"+b
def reduceTextM[A](a:A,b:A)=a+":"+b

reduceText(1,2)                           //> res1: String = 1:2
reduceText('1','2')                       //> res2: String = 49:50
//BŁĄÐ KOMPILACJI //reduceText("1","2")
reduceTextM(1,2)                          //> res3: String = 1:2
reduceTextM('1','2')                      //> res4: String = 1:2
reduceTextM("1","2")                      //> res5: String = 1:2

lista.reduce(reduceTextM)                 //> res6: Any = 1:2:3:4:5
val lista2=List('a','b','c')                    //> lista2  : List[Char] = List(a, b, c)
lista2.foldLeft("")(reduceTextM)          //> res7: String = :a:b:c

zadanie : treść podobna "zdublować przy pomocy flatMap wszystkie elementy listy List(1,2,3) -> List(1,1,2,2,3,3)" ale tym razem w dwóch wersjach
  1. Funkcji, która przyjmuje listę typu Int i wykonuje wspomnianą operację
  2. Metody, która przyjmuje jakąś listę typu A i wykonuje wspomnianą operację
val double=(l:List[Int])=>l.flatMap(e=>List(e,e))
def doubleM[A](l:List[A])=l.flatMap(e=>List(e,e))

A jak to wygląda w Javie ?
Stream stream = Stream.of(1,1,2,1,3,1,4,5);
  Optional result = stream.reduce((a,b)->a+b);
  result.ifPresent(System.out::println);

Grupowanie

Tutaj zaś zastapimy działanie na intach działaniem na bardziej domenowych obiektach. Zobacyzmy tkaże ile więcej kodu trzeba napisac w Javie by osiągnąc podobny rezultat:(

case class User(name:String,age:Int,gender:String)
 val lista=List(User("Stefan",28,"M"),User("Joanna",20,"F"),User("Elżbieta",28,"F"))
                                                  //> lista  : List[akademia.kolekcje.User] = List(User(Stefan,28,M), User(Joanna,
                                                  //| 20,F), User(Elżbieta,28,F))

 lista.groupBy(_.age)                      //> res0: scala.collection.immutable.Map[Int,List[akademia.kolekcje.User]] = Map
                                                  //| (20 -> List(User(Joanna,20,F)), 28 -> List(User(Stefan,28,M), User(Elżbieta
                                                  //| ,28,F)))
 lista.groupBy(_.gender)                   //> res1: scala.collection.immutable.Map[String,List[akademia.kolekcje.User]] = 
                                                  //| Map(M -> List(User(Stefan,28,M)), F -> List(User(Joanna,20,F), User(Elżbiet
                                                  //| a,28,F)))
eksperymenty :
val random=new java.util.Random()
import Math.abs
val users=List.tabulate(100)(n=>User((n % 30).toString,abs(random.nextInt()) % 30,"F"))
users.groupBy(_.age)
users.groupBy(_.name) 

W javie niestety trzeba się trochę więcej rozpisać :
class User{
 private final String name;
 private final int age;
 public User(String name, int age) {
  this.name = name;
  this.age = age;
 }
 public String getName() {
  return name;
 }
 public int getAge() {
  return age;
 }
 @Override
 public String toString() {
  return "User [name=" + name + ", age=" + age + "]";
 }
}
List<User> users = asList(new User("ccc",12),new User("bbb",20),new User("aaa",12));
Map<Integer, List<User>> result = users.stream().collect(Collectors.groupingBy(User::getAge));

result.forEach((k,vl)->System.out.println(k+":"+vl));

Span i partition

Niech kod przemówi :
val numery=List(1,4,5,78,9,1,2,3)         //> numery  : List[Int] = List(1, 4, 5, 78, 9, 1, 2, 3)
 
 
 numery.partition(_%2==0)                  //> res0: (List[Int], List[Int]) = (List(4, 78, 2),List(1, 5, 9, 1, 3))
 numery.partition(_>3)                     //> res1: (List[Int], List[Int]) = (List(4, 5, 78, 9),List(1, 1, 2, 3))
 numery.partition(_<10)                    //> res3: (List[Int], List[Int]) = (List(1, 4, 5, 9, 1, 2, 3),List(78))
 numery.span(_<10)                         //> res2: (List[Int], List[Int]) = (List(1, 4, 5),List(78, 9, 1, 2, 3))
zadanie : stworzyć listę od 0 do 100 i podzielić ją na dwie listy : w jednej mają być potęgi dwójki a w drugiej liście cała reszta.
import math._
val powersOfTwo=List.range(0, 10).map(n=>pow(2, n).toInt).toSet
List.range(0,100).partition(powersOfTwo contains _)

Widok

Tutaj jest różnica z javą.
val numery=List(1,4,5,78,9,1,2,3)         //> numery  : List[Int] = List(1, 4, 5, 78, 9, 1, 2, 3)
 
// zwrocic uwage na SeqViewMMF 
numery.view.map(_+1).map(_+2).filter(_>4) //> res0: scala.collection.SeqView[Int,Seq[_]] = SeqViewMMF(...)

Zipy i Unzipy

val numery=List.range(13, 27)             //> numery  : List[Int] = List(13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,  26)
numery.zipWithIndex                       //> res0: List[(Int, Int)] = List((13,0), (14,1), (15,2), (16,3), (17,4), (18,5),
                                                  //|  (19,6), (20,7), (21,8), (22,9), (23,10), (24,11), (25,12), (26,13))                                                 
 
 for{
  (e,i) <- numery.zipWithIndex
  } yield s""" """
                                                  //> res1: List[String] = List( ,  ,  ,  ,  ,  ,  ,
                                                  //|   ,  ,  
                                                  //| ,  ,  ,  ,  )
zadanie : stworzyć listę od 0 do 10 i usunąć co trzeci element
val lista=List.range(1,10)
lista.zipWithIndex.filter{case (_,i) => (i+1)%3!=0}.map(_._1)

Mutable i Immutable

val numery=List(1,2,3,4)                  //> numery  : List[Int] = List(1, 2, 3, 4)
 
 numery.+:(5)                              //> res0: List[Int] = List(5, 1, 2, 3, 4)
 numery.+:(5)                              //> res1: List[Int] = List(5, 1, 2, 3, 4)

 
  import scala.collection.mutable.ListBuffer
  val numeryBuffer=ListBuffer(1,2,3,4)            //> numeryBuffer  : scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 2, 
                                                  //| 3, 4)
  
  numeryBuffer += 5                               //> res2: poligon.poligon.numeryBuffer.type = ListBuffer(1, 2, 3, 4, 5)
  numeryBuffer += 5                               //> res3: poligon.poligon.numeryBuffer.type = ListBuffer(1, 2, 3, 4, 5, 5) 

For

val numbers=Vector(1,2,3,4)
val letters=Vector('a','b','c','d')

for{
  n<-numbers
  l<-letters
 } yield (n,l)

val names = Map("firstname" -> "Roman", "lastname" -> "Kowalski")

for ((k, v) <- names) println(s"key : ${k} , name : ${v}")
eksperymenty :
numbers.map(n=>
    letters.map(e=>s"($n,$e)")
   )                                              //> res1: scala.collection.immutable.Vector[scala.collection.immutable.Vector[St
                                                  //| ring]] = Vector(Vector((1,a), (1,b), (1,c), (1,d)), Vector((2,a), (2,b), (2,
                                                  //| c), (2,d)), Vector((3,a), (3,b), (3,c), (3,d)), Vector((4,a), (4,b), (4,c), 
                                                  //| (4,d)))

   numbers.flatMap(n=>
    letters.map(e=>s"($n,$e)")
   )                                              //> res1: scala.collection.immutable.Vector[String] = Vector((1,a), (1,b), (1,c)
                                                  //| , (1,d), (2,a), (2,b), (2,c), (2,d), (3,a), (3,b), (3,c), (3,d), (4,a), (4,b
                                                  //| ), (4,c), (4,d))