niedziela, 26 lipca 2015

Wstrzykiwanie po fakcie - czyli funkcje w życiu codziennym

Kiedyś na bazarze kupiłem książkę do karate za 12 złotych (tak - jest coś takiego jak książka do karate, są tam obrazki i nawet sekcja jak walczyć przy pomocy parasola lub jak obezwładnić przeciwnika kantem stołu). Poza kolejnymi ilustracjami przedstawiającymi fazy kopa z półobrotu jest tam także wstęp filozoficzny o nauce.

AbstraHUJąc od masy japońskich słówek generalnie chodzi o to, aby po osiągnięciu pewnego poziomu umiejętności cofnąć się niejako do poprzedniego etapu tak by uprzednio zdobyta wiedza nie zaowocowała uprzedzeniami przy zdobywaniu nowej.

Co się stanie gdy pozostaniemy na abstrakcyjnym poziomie specjalisty? Jeśli np. opanowaliśmy kop z obrotu i przejdziemy do nauki ciosu podbródkowego (coś jak mortal kombat) to dopiero po odpowiedniej ilości ćwiczeń możemy posługiwać się techniką w odpowiedni sposób. Gdy przysiądziemy do tego z nastawieniem "jestem specem" to przy pierwszych niepowodzeniach może pojawić się racjonalizacja "coś mi to nie wychodzi, ale przecież jestem specem, jestem zwycienzcom - AHA! - czyli to jest głupie, to jest głupie - wracam do Javy... tfu znaczy do kopów z półobrotu"

jak działa IF?

Gdybyśmy mieli komuś kto nie wie co to jest if wytłumaczyć czym on jest i po co on jest - jakbyśmy do tego podeszli?

  1. Pierwszy sposób to najpierw kilka prostych ćwiczeń oderwanych od rzeczywistości aby zrozumieć mechanikę a później jakiś przykład z życia (np if(isLogged(user))) by zrozumieć sens
  2. Podejście drugie : dwugodzinna dyskusja o wyższości jednego rozwiązania nad drugim po czym wszyscy idą do domu i się nie odzywają do siebie
I pomimo iż sposób drugi jest bardzo często praktykowany w społecznościach IT - szczególnie w kontekście nauki FP - to my spróbujemy podejścia numer 1

Cel ćwiczenia

Ćwiczenie jest wzięte z książki : "Functional and Reactive Domain Modeling"


Mamy taką oto implementację Serwisu
trait AccountService[Account, Amount, Balance] {
def open(no: String, name: String, openingDate: Option[Date]):
AccountRepository => Try[Account]
#A
def close(no: String, closeDate: Option[Date])
AccountRepository => Try[Account]
def debit(no: String, amount: Amount): AccountRepository => Try[Account]
def credit(no: String, amount: Amount): AccountRepository => Try[Account]
def balance(no: String): AccountRepository => Try[Balance]
}

Kodu może wydawać się trochę pokręcony - co by nie było jest on wyciągnięty z końcówki rozdziału - toteż skoncentrujmy się na razie jedynie na typie zwracanym: AccountRepository => Try[Account].

Ten typ to zwykła funkcja i przez taką formę wyniku możemy wykonać operacje na repozytorium... bez podawania repozytorium. Dokładnie. Jest to forma dependency injection, której jeszcze nie widziałem - czyli wstrzykiwanie już niejako po operacji.

object App {
import AccountService._
def op(no: String) = for {
_ <- credit(no, BigDecimal(100))
_ <- credit(no, BigDecimal(300))
_ <- debit(no, BigDecimal(160))
b <- balance(no)
} yield b
}

I następnie :
scala> import App._
import App._
scala> val domainAction= op("a-123")
domainAction: AccountRepository => scala.util.Try[Balance] = <function1>

scala> domainAction(domainRepository)
...

I to działa*...tylko ku*wa jak?! (* - działa jak w dowolny sposób doda się map i flatMap do Function1 ale o tym za chwilę)

Aby zrozumieć co tam się dzieje robimy labolatorium (jak za starych czasów na TVP1)

Jak działa map?

Pierwsza rzecz to nie ma co rysowac od nowa tych obrazków bo jest ich masa w sieci
1. Tutaj jest pierwszy
2. A tutaj następny

Od strony mechaniki to będzie wyglądało tak :

val f=(i:Int)=>i+1
List(1,2,3) map f
Some(1) map f

f: Int => Int = 
res8: List[Int] = List(2, 3, 4)
res9: Option[Int] = Some(2)

I możemy nawet dla porównania zrobić w Javie jak ktoś lubi
Function<Integer,Integer> f = (Integer i) -> i + 1;
    Stream.of(1,2,3).map(f).forEach(out::println);
    Optional.of(1).map(f).ifPresent(out::println);

A jak działa map na funkcji?

Z tego co widziałem i doświadczyłem ludziom na etapie "Java 1-7" trochę grzeje banie fakt traktowania funkcji jako argumentu innej funkcji czy też rezultatu zwracanego jako funkcji. W związku z tym analiza tego przykładu może zając chwilę.

val f=(i:Int)=>i+1
((i:Int)=>i+2) map f
//lub
val f2=(i:Int)=>i+1
val fmapped=f2 map f
fmapped(1)

f: Int => Int = 
res8: Int => Int = 
//lub
f2: Int => Int = 
fmapped: Int => Int = 
res9: Int = 4

Aby to zadziałało trzeba albo samemu dodać map do Function1 albo zaimportować jakaś bibliotekę, która to robi za nas. Z rezultatu (3) widzimy, że niejako dwie funkcje (+1) i (+2) zostały złożone i jeśli spojrzymy w implementację to faktycznie tak to wygląda
implicit def Function1Functor[R]: Functor[...cuda...] {
    def fmap[A, B](r: R => A, f: A => B) = r andThen f
  }

jak działa flatMap

List(1,2,3).map(i=>List(i,i))
List(1,2,3).flatMap(i=>List(i+1))
Some(1).map(i=>Some(i+1))
Some(1).flatMap(i=>Some(i+1))

i wynik
res8: List[List[Int]] = List(List(1, 1), List(2, 2), List(3, 3))
res9: List[Int] = List(2, 3, 4)
res10: Option[Some[Int]] = Some(Some(2))
res11: Option[Int] = Some(2)

A w Javie
Stream.of(1,2,3).map(i->Stream.of(i)).forEach(out::println);
Optional.of(1).map(i->Optional.of(i)).ifPresent(out::println);
Stream.of(1,2,3).flatMap(i->Stream.of(i)).forEach(out::println);
Optional.of(1).flatMap(i->Optional.of(i)).ifPresent(out::println);

Po co umieszczać jeszcze przykłady w Javie? Głównie dla porównania (ale tez aby oduczyć ludzi, ze porównywanie Java vs Scala to porównanie Obiektowo vs Funkcyjnie)

Mindfuk start - flat map dla funkcji

To jest coś co dla mnie nie było na początku intuicyjne ale po zerknięciu w implementację

implicit def Function1Bind[R]:  = new Bind[...cuuuuda....] {
    def bind[A, B](r: R => A, f: A => R => B) = (t: R) => f(r(t))(t)
}

Pierwsza rzecz dlaczego nazywa się to bind? Bo w haskellu nazywa się to bind a scali i javie flatMap a w scalaz wzorowali się chyba na haskellu(deal with it).

No i generalnie flatMap ma ogólnie sygnaturę Cos.flatMap(arg=>Cos) i też tutaj jest podobnie F.flatmap(arg=>F)

// implementacja (t: R) => f(r(t))(t)
// dla 1 f(r(1))(1) => f(2)(1) => 2+1 => 3
// dla 2 f(r(2))(2) => f(3)(2) => 3+2 => 3
val f2=(i:Int)=>i+1
val fflatmapped=f2 flatMap(a=>b=>a+b)
fflatmapped(1)
fflatmapped(2)

O co chodzi z tym podkreślnikiem?

// implementacja (t: R) => f(r(t))(t)
// dla 1 f(r(1))(1) => f(_)(1) => 1 => 1
// dla 2 f(r(2))(2) => f(_)(2) => 2 => 2
val f2=(i:Int)=>i+1
val fflatmapped=f2 flatMap(_=>b=>b)
fflatmapped(1)
fflatmapped(2)

W tym przypadku centralnie zlewamy wynik wywołania pierwszej funkcji- czy to ma sens? Tylko w jednym przypadku...
// implementacja (t: R) => f(r(t))(t)
// dla 1 f(r(1))(1) => f(_)(1) => 1 => 1
// dla 2 f(r(2))(2) => f(_)(2) => 2 => 2
val f2=(i:Int)=>{
  println("side effect!!")
  i+1
}
val fflatmapped=f2 flatMap(_=>b=>b)
fflatmapped(1)
fflatmapped(2)

Czas na połączenie elementów ukladanki...

map, flatMap, funkcje i repozytorium

class Repo{
  def save(i:Int,actionId:String):Option[Int]={
    println(s"saving $i in $actionId")
    Some(i)
  }
}
def action1(domainArgument:Int):Repo=>Option[Int]={repo=>
  repo.save(domainArgument,"akcja1")
}

def action2(domainArgument:Int):Repo=>Option[Int]={repo=>
  repo.save(domainArgument,"akcja1")
}

val domainAction=action1(1).flatMap(_=>action2(1))
domainAction(new Repo())

Nie wiem czy ten kawałek kodu będzie jasny od początku. Generalnie udało nam się zbindować (AHA! dlatego to nazywa się bind - oczywiście źle, ze używam słów zapożyczonych z angielskiego) kilka operacji które będą wykonane na repo bez specyfikowania skąd wziąć to repo. Czy to jakas forma https://en.wikipedia.org/wiki/Late_binding - być może...

I podobny rezultat :

def wio(entry:Int)=for{
  _ <- action1(entry)
  result <- action2(entry)
} yield result

val domainAction=wio(1)
domainAction(new Repo())

saving 1 in akcja1
saving 1 in akcja1
res12: Option[Int] = Some(1)

Jedno małe ale...

Małe ale polega na tym, że w tzw. "for-comprehension" ostatnim elementem musi być map a u nas jest flatMap. Ponieważ rozwiązanie oczywiste mi umykało z przed oczu i metoda prób i błędów nie mogłem znaleźć rozwiązania co to map maiłoby robić toteż zbudowałem Zestaw młodego dekompilatora

W SBT

initialCommands in console := "import scalaz._, Scalaz._"

:paste i wklei https://gist.github.com/Luegg/7449370
scala> desugar{
     | for{
     |   _ <- action1(1)
     |   result <- action2(1)
     | } yield result
     | }


I wynik
scalaz.Scalaz.ToBindOpsUnapply[Repo => Option[Int]]($line18.$read.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.action1(1))
(scalaz.this.Unapply.unapplyMAB2[scalaz.Bind, Function1, Repo, Option[Int]]
(scalaz.Scalaz.function1Covariant[Repo])).flatMap[Option[Int]]
(((_: Option[Int]) => scalaz.Scalaz.ToFunctorOpsUnapply[Repo => Option[Int]]
($line18.$read.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.action2(1))
(scalaz.this.Unapply.unapplyMAB2[scalaz.Functor, Function1, Repo, Option[Int]]
(scalaz.Scalaz.function1Covariant[Repo])).map[Option[Int]](((result: Option[Int]) => result))))

Nie ma co się przerażać tym kodem gdyż interesuje nas tylko końcówka : .map[Option[Int]](((result: Option[Int]) => result))
I to jest to oczywiste rozwiązanie :
val domainAction=action1(1).flatMap(_=>action2(1).map(i=>i))(new Repo())
val domainAction2=action1(1).flatMap(_=>action2(1).map(identity))(new Repo())

Chyba

niedziela, 12 lipca 2015

Nauka programowania funkcyjnego poprzez synestezję

Pierwsze wnioski po dwóch warsztatach z programowania funkcyjnego wyglądają tak :

I oczywiście pole powierzchnia kółek oraz cześć wspólna jest wzięta z d*** - tudzież na oko - i ma tylko znaczenie symboliczne nie zaś reprezentatywne.

Plan była taki, iż najpierw będzie trochę warsztatów ze scali gdzie ludzie nauczą się jej składni po czym będzie można przejść do praktycznych zastosowań. I jednak nie można przyjść do tych zastosowań bo tematem są zainteresowani ludzie, którzy twierdzą, ze takie konstrukcje jak w scali to widzieli w perlu 20 lat temu. No mają swoje zdanie - ja mam inne ale nie mogę zaprzeczać, że największą trudnością w nauce przy pomocy przykładów, które przygotowałem na warsztaty była własnie składnia scali.

Można popróbować kilku podejść w przyszłości - można uśmiechnąć się w kierunku Javy8 - chociaż po przejściu progu nauki Scali uważam, że to składnia Javy jest trochę okaleczona ale w swym okaleczeniu na tyle spójna, że przystępna do nauki.

Można wrócić do próby nauki scali przy pomocy nauki jakiegoś praktycznego narzedzia jak to było z playem.

A można w końcu wziąć przykład z placówek (hahaha własnie przeglądam tekst po raz drugi i zauważyłem, że zamiast "placówek" napisałem "palcówek" - If yoy know what I min) gdzie efektywność nauki jest chyba największa na świecie - mowa oczywiście o przedszkolach...

Ale zanim co - to jedyny filmik jaki kiedykolwiek wrzuciłem na youtube - było to prawie dwa lata temu kiedy zaczynałem prowadzić pierwsze wykłady z programowania funkcyjnego. Wydaje mi się, że po dwóch latach nauki w końcu mogę pomału zacząć robić to poprawnie.

Co to jest synestezja

https://pl.wikipedia.org/wiki/Synestezja
Synestezja (gr. synaísthesis – równoczesne postrzeganie od sýn – razem i aísthesis – poznanie poprzez zmysły) - w psychologii stan lub zdolność, w której doświadczenia jednego zmysłu (np. wzroku) wywołują również doświadczenia charakterystyczne dla innych zmysłów, na przykład odbieranie niskich dźwięków wywołuje wrażenie miękkości, barwa niebieska odczuwana jest jako chłodna, obraz litery lub cyfry budzi skojarzenia kolorystyczne itp.

To co spróbujemy zrobić to nie będzie taka raczej pseudo-synestezja gdyż jakowoż postaramy się wywołać wrażenia zmysłu matematycznego (który w praktyce nie istnieje) poprzez działanie na zmysł wizualny

Mając np. takie typy

type A
type B
type C

Zazwyczaj przedstawiamy serię abstrakcyjnych przykładów stymulując zmysły matematyczny (i nie wiem czy w praktyce istnieje coś takiego jak zmysł matematyczny ale żym słyszał o czymś takim jak indywidualna inteligencja matematyczna)

val fun1: A => B = ???
val fun2: B => C = ???
val andThen: A => C = fun1 andThen fun2

I to bylibymy jeszcze w stanie wytłumaczyć ale co jak dojdziemy do "ciekawszych konstrukcji"

val twoParamFun: (A, B) => C = ???
val curry: (A, B) => C => A => B => C = ???

Czy żeby już się tak definitywnie popisać  :

trait Functor[F[_]] {
  def map[A, B](fa: F[A])(f: A => B): F[B]
}

Da się ludzi i tak nauczać ale fakt, że "baj default" statystyczny (podkreślam statystyczny) student posadzony do kodu pisze kod proceduralny a nie abstrakcyjny kod modularny może wskazywać, że konkret a nie abstrakcja jest znowu "statystycznym" poziomem podejścia do problemu. a gdyby tak...

type GREEN
type YELLOW
type REED

... i odpowiednio ...

val fun1: GREEN => YELLOW = ???
val fun2: YELLOW => REED = ???
val andThen: GREEN => REED = fun1 andThen fun2
val twoParamFun:(GREEN, YELLOW) => REED = ???
val curry:(GREEN, YELLOW) => REED => GREEN => YELLOW => REED = ???

Ewentualnie jak ktoś miał dzieciństwo jeszcze przed fejsbukiem

W stronę bardziej abstrakcyjnych konstrukcji

Oto chyba jeden z najbardziej testowalnych kawałków kodu

val f: Int => Int = x => x + 1

Jest tez super re używalny bo moge go zastosować wszędzie tutaj :

List(1, 2, 3) map f
Some(1) map f
Try(1) map f
Future(5) map f

Jak i powymyslać swoje konstrukcje

object Produkt {
  type NiezwykleSpecjalistycznaDomenowaNazwaZeAzDechZapiera = Int => Int
  def zmienCene(f: NiezwykleSpecjalistycznaDomenowaNazwaZeAzDechZapiera) = ???
}

Produkt zmienCene f

Ten tekst jest pogrubiony bo teraz będzie coś bardzo waznego. Dopiero niedawno to załapałem :

type NormalType = Int
type HigherType = List[Int]

val f: NormalType => NormalType = ???
val specialFunction: (NormalType => NormalType) => (HigherType) => HigherType = ???
val fh: HigherType => HigherType = specialFunction(f)

Co tutaj się dzieje? Udało nam się napisać funkcję która zmienia prymitywną, łatwą do testowania, oczywista funkcyjkę na coś - bardziej złożonego, mniej reuzywalnego ale potezniejszegow danym kontekście. Innymi słowy podnieśliśmy funkcję f:Int=>Int do funkcj f:List[Int] => List[Int] stąd też nazwa lift. I dalej kilka takich praktyczniejszych przykładów.

val f = (x: Int) => x + 1


val liftToList = (f: Int => Int) => (la: List[Int]) => la map f
val liftedList = liftToList(f)
liftedList(List(1, 2, 3)) //res3: List[Int] = List(2, 3, 4)


val liftToOption = (f: Int => Int) => (oa: Option[Int]) => oa map f
val liftedOption = liftToOption(f)
liftedOption(Some(1)) //res4: Option[Int] = Some(2)

I patrzcie jaki to ma efekt i co się dzieje z przekazanymi obiektami typu List i Option. A teraz mindfuck - można zmapowac funkcję sama ze sobą (po imporcie scalaz)

val mappedF = f map f
mappedF(1) // res5: Int = 3

val liftToFunction = (f: Int => Int) => (fa: Int => Int) => fa map f
val liftedFunction = liftToFunction(f)
liftedFunction(x => x + 2)(1) // res6: Int = 4

Aż zerknąłem jak to jest zaimplementowane...

/** Wraps a value `self` and provides methods related to `Functor` */
final class FunctorOps[F[_],A] private[syntax](val self: F[A])(implicit val F: Functor[F]) extends Ops[F[A]] {
  ////
  import Leibniz.===

  final def map[B](f: A => B): F[B] = F.map(self)(f)

Wydaje się, że Leibniz to miły człowiek...
W każdym razie lift to by było coś w ten deseń

type GREEN
type YELLOW
type REED

class Shape[A]()

val greenShape = new Shape[GREEN]()
val f: GREEN => YELLOW = ???

val liftToShape: (GREEN => YELLOW) => (Shape[GREEN]) => Shape[YELLOW] = ???

val lifted: (Shape[GREEN]) => Shape[YELLOW] = liftToShape(f)

I na koniec klasyczny argument

"Nie ku*wa wypie*dalać z tym bo kto to będzie utrzymywał jak nikt tego nie zna" - tak... spalmy samochody bo nikt na wiosce nie ma prawa jazdy i tylko syn starosty kiedyś jechał traktorem po grządce ziemniaków.

No jasne, że nie będziemy w punkcie czasu t1 budować dzwigiem jak 98% programistów na rynku umie używać tylko młotka (statystyka wzięta z dupy) ale może by tak się pokusić by w czasie t2 już jakaś załoga umiała ten dźwig obsługiwać a to się nie stanie jak wszyscy do okoła będą optymalizować swoje minima lokalne na Q3 i rzucać tylko cytatami z 7 minutowego managera.

Tudzież zapraszam serdecznie na odbywające się cyklicznie warsztaty i wykłady JUGa gdzie w miłej atmosferze i niejedno kroć darmowym jedzeniu można jakże przyjemnie wchłaniać wiedzę budując lepsze jutro zarówno dla siebie jak i szeroko rozumianej lokalnej społeczności IT.