środa, 11 maja 2016

Typy Danych ale Algebraiczne

Często nie ma co zbyt pochopnie wydawać oceny gdyż wiele zależy od sytuacji i kontekstu :

  • Widzisz kolesia z tasakiem we krwi - krzyczysz "Policja!" - a to tylko rzeźnik wraca z fajki.
  • Widzisz jak koleś wpada do banku z kałachem - krzyczysz "Policja!" - a to tylko kręcą "Nowe przygody Porucznika Borewicza"
  • Widzisz kolesia, który w klasie abstrakcyjnej jawnie odwołuje się do typów, które ją rozszerzają - krzyczysz "Policja Obiektowa !" - a to tylko ktoś sobie implementuje Algebraiczne Typy Danych

"to zależy"

Programiści to ludzie a ludzie lubią jak są jasne zasady, posortowane ,dobro-zło, po przydzielane do danej kategorii i zaszufladkowane. "Czy to jest dobre rozwiązanie? To zależy..." - to niektórych bardzo denerwuje i frustruje bo psuje ścisłe prawa rządzące ich światem.

Ta książka :
Strona 117 (edycja polska, wydawnictwo Helion, copyrajt 2010 itd) :

Jeśli dobrze kojarz Unkel Bob prowadzi firmę "ObjectMentor" (dla młodzieży - Unkel Bob - Robert C.Martin - autor Clean Code) no i jak on sam twierdzi, że nie wszystko jest obiektem i czasem jak coś nie jest obiektem to dobrze, że nie jest obiektem - to może również na pytanie "czy dobre zasady programowania obiektowego są zawsze dobre " - może też zależy? (jak np. "czy bieganie jest zdrowe? No jak stoisz na polu minowym to nie jest- także zależy.")

"Otwarty na rozbudowę / zamknięty na polemikę"

Jest zasada Otwarty na rozbudowę / zamknięty na modyfikację i ogólnie jest to dosyć dobra zasada ale problem polega na tym, że można ją interpretować. Dokładnie tak - programiści ją interpretują i stosują swoją interpretację. Moją interpretacją przez kilka ładnych lat było to, że "trzeba tak napisać klasy i interfejsy, żeby łatwo nowe klasy implementowały te interfejsy a stare klasy były nie ruszane".

Jednak można znaleźć przykłady gdzie zabronienie powyższego procederu tylko wyszło na dobre! Np w uproszczeniu Option w Scali wygląda tak :

sealed trait Option[+A]
final case class Some[+A](get : A) extends Option[A]
case object None extends Option[Nothing]

Sealed to bardzo interesujący mechanizm, którego chyba nie ma w Javie. To takie final ale tylko dla kodu, który nie znajduje się w tym samym pliku - sprawia to, że możemy stworzyć lokalną rodzinę typów zamknięta na dalsza rozbudowę (rozbudowę w sensie dokładania nowych typów bo można o nowe operacje). Nie jestem historykiem-archeologiem ale wydaje mi się, że ten Option jest w Scali od dosyć dawna. W Javie jest Optional, które jest "public final class" i tez ogólnie na rozszerzanie jest zamknięte (i może własnie brak mechanizmu sealed w Javie spowodował, że tam Optional jest zaimplementowany jako taka wolno stojąca pojedyncza klasa ? Nie wiem, być może - to taki strzał z dupy)

Jest jeszcze inny ciekawy przykład...

Jaka cena?

To jest przykład , który z jednej strony ma szanse pokazać, że i czasem przez ponad 150 lat nie ma potrzeby na "rozbudowę typu". I pomimo, że typ nie był rozbudowywany to jest niezwykle użyteczny i praktyczny. Do tego cały czas się ciągnie to słowo Algebraiczne, które brzmi bardzo abstrakcyjnie i matematycznie.

Ów przykład to Algebra Boole'a - i pojawia się tam to słowo Algebra a generalnie może się pojawić bo typy i operacje są jasno określone bo ograniczone . Co by się stało gdyby typ Boolean był otwarty na rozbudowę w rozumieniu "dodawania nowych typów rozszerzających Boolean?"

Jak mamy :

if(costam) {
wariant1
}else{
wariant2
}

To wiadomo, że albo "costam" to albo false albo true (poza językami, gdzie pusta tablica to też false ale to inna historia). I przez to, że jest tak ubogo to jest prosto, a jak jest prosto - to jest bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo bardzo dobrze.

Jak jest prosto to jest bardzo dobrze i tworzenie abstrakcji gotowej na rozbudowę może mieć swoje uzasadnienie ale ma tez swoją cenę!. I jak np. napiszemy kod bardzo abstrakcyjny to nie jest tak, że "dobre bo solid kurde solid" tylko trzeba zadać tez sobie pytanie czy jesteśmy świadomi ceny i czy potrzeba tę cenę uzasadnia.

Czyli to że Boolean to tylko True i False to dobrze - uwaga! "To zależy" . Tak na codzień to chyba dobrze ale już w poniższych linkach wygląda tak trochę jakby nie wystarczyło

Koniec filozofowania

Dobra pisanie tekstu zamiast kodu ma tę wadę, ze muszę potem sprawdzić czy nie ma w nim błędów bo spellcheckerowi daleko do kompilatora. Także tego...

W Scali jakbyśmy chcieli takiego Booleana rezem z operacjami zaimplementować to tak :

sealed trait Bul{
  def and(other:Bul)= this match {
    case Fols => Fols
    case Tru  => other
  }
}
case object Tru extends Bul
case object Fols extends Bul

I teraz mamy rzecz niesłychaną!!! Oto w klasie (a właściwie traicie) abstrakcyjnej mamy Pattern Matching z konkretnymi typami. Ale słuchaj, słuchaj, wdech-wydech, posłuchaj. Wyłącz na chwile reakcję automatyczną ciała migdałowatego i pomyśl - i tak nie dorzucimy żadnego nowego typu, wszystko jest jasno określone tu i teraz więc czemu by tej wiedzy nie wykorzystać? Ano wykorzystujemy.

I w scali fajne jest to, co jeśli źle użyte będzie przeklęte - można czasem olać kropki i nawiasy i wtedy mamy :

println(Tru and Tru) //Tru
println(Tru and Fols) //Fols
No dla mnie wygląda to fajnie. Ale można jeszcze inaczej :
object BulAlgebra{
  def and(left:Bul,right:Bul):Bul=(left,right) match{
    case (Fols,Tru) => Fols
    case (Tru,Tru) => Tru
  }
}
I jest tutaj specjalnie zrobiony błąd bo nie wszystkie opcje są rozpatrzone :
Warning:(29, 35) match may not be exhaustive.
It would fail on the following inputs: (Fols, Fols), (Tru, Fols)
  def and(left:Bul,right:Bul):Bul=(left,right) match{
                                  ^

I zobaczcie co zyskaliśmy przez to, że nie tolerujemy open/close principle po obiektowemu - wszystkie typy są w jednym miejscu i kompilator może mi zwrócić pożyteczną uwagę "o czymś żeś zapomniał i patrz wyjebie się - nie ma za co"

To była Scala a teraz coś z zupełnie innej beczki

Idris

Idris to język - który wygląda jak Haskell plus ma bonusy - taki Super Sajan Haskellz rodziny "bardzo dobrze zaprojektowane ale mało kto umie używać" (tego typu języki napotykają dziwną barierę w organizacjach gdzie "wzrost" jest mierzony "hed kantem"). I generalnie tutaj to normalnie jest Baaaaaaaaaaajka (nie mylić z tym klubem w Mielnie).

data Bul = Tru | Fols

and : (Bul,Bul) -> Bul
and (Tru, Fols) = Fols
and (Tru,Tru) = Tru

I znowu jest specjalny błąd ale tutaj do problemu podejdziemy trochę od innej strony. W Idris możemy sprawdzić czy funkcja jest całkowita czy częściowa gdyż kompilator ma wszystkie informacje o typie - fachowo to się nazywa totality checker.

I po uzupełnieniu :

and : (Bul,Bul) -> Bul
and (_, Fols) = Fols
and (Fols, _) = Fols
and (Tru,Tru) = Tru

I bardzo dobrze. I naprawdę podoba mi się ta nazwa "Totality Checker" - jak taki cyfrowy superbohater.

Kotlin

Kotlin jest o tyle ciekawym językiem, że został stworzony przez tę samą firmę, która stworzyła jedno z lepszych (a być może najlepsze) IDE dla programistów Javy - JetBrains. I to co jest niezwykle ciekawe,to że firma z kilkunastoletnim doświadczeniem z parsowania na lewo i prawo składni Javy stworzyła dla swojego języka składnię podobną bardziej...do Scali. Dokładnie tak - Kotlin jest podobny bardzo do Scali niż Javy - a do tego JetBrains reklamuje go jako język bardzo praktyczny i ma wszelkie "kwalifikacje" aby twierdzić, że wie jak powinien wyglądać język praktyczny. Poniżej przykład ADT w Kotlinie - co moim zdaniem jest bardzo dobre to fakt, że jeśli ten ichniejszy pattern matching z "when" ma brakujące przypadki to kod zwyczajnie się nie kompiluje.

sealed class Bul{
    object Tru : Bul()
    object Fols : Bul()

    fun and(other:Bul):Bul=when(this){
        is Fols -> Fols
        is Tru -> other
    }
}

Także może należy przenieść dyskusje "składnia Javy i składnia Scali" na nowy poziom - "Składnia języków z lat 90tych i składnia nowych języków?". Generalnie kto zna Scalę powinien szybko załapać Kotlina a kto zna Javę -cóż... będzie miał okazję poznać wiele mechanizmów, o których istnieniu nie miał pojęcia.

Brak komentarzy:

Prześlij komentarz