For comprehensions – o Scali ciąg dalszy

For comprehensions – o Scali ciąg dalszy
Z for comprehensions jest jak z dobrym filmem lub serialem. Jak jeszcze o nim nie słyszałeś, to zazdroszczę Ci, bo czeka Cię nie lada niespodzianka! Jak już je znasz, to wiesz, że jest to must-have. Pozwolę sobie nie tłumaczyć wyrażenia for comprehensions na język polski, ponieważ do tej pory nie spotkałem się z jego dobrym tłumaczeniem. Zresztą niektórych wyrażeń nie ma sensu tłumaczyć, przecież nie używa się „Rubin” zamiast Ruby. Albo „wieszczbiarz” zamiast Python (Tak – takie tłumaczenie sugeruje Google 🙂 ). 
tlumaczenie for-comprehensions nie jest latwe - tak jak python

Zacznijmy od definicji

Dokumentacja Scali podaje, że for comprehensions przyjmuje formę:

 

for (enumerators) yield e

Enumerators – enumerator lub lista enumeratorów oddzielona średnikami. Może być to również filtr dla wartości przetwarzanych (jak w listingu nr 1). Całe wyrażenie definiuje ciało e dla każdej wartości wywołanej przez enumerator i zwraca je w postaci sekwencji.

Prześledźmy co dzieje się w listingu nr 1. Linie od 1 do 7 to deklaracja case class Osoba a następnie utworzenie listy osób – List[Osoba]. Każda z osób ma przypisane imię oraz swój wiek. Następnie mamy nasze for comprehensions. Linia nr 9 to tak zwany generator – każdy element z listy spisOsob zostanie przypisany do zmiennej osoba. W następnej linii znajduje się filtr, który wybiera jedynie te elementy, które spełniają podany warunek. Tym warunkiem jest wiek większy niż 30 (włącznie) i mniejszy niż 40 lat. Wiemy, że jest tylko jedna osoba, która spełnia ten warunek – Pan Czarek. Jeżeli element przejdzie przez filtr, to yield doda imię osoby do listy wynikowej. W powyższym przypadku uzyskaliśmy jednoelementową listę zawierającą String o wartości „Czarek”.

Więcej niż filtrowanie listy

case class Student(imie: String, nazwisko: String)

val imieOpt: Option[String] = Some("Jerzy")
val nazwiskoOpt: Option[String] = Some("Jurkowski")

val student = for {
    imie <- imieOpt
    nazwisko <- nazwiskoOpt
  } yield Student(imie, nazwisko)
  
println(student.getOrElse("Pusto :( "))
  //Wypisze: Student(Jerzy,Jurkowski)
  

Listing nr 2

Tak jak wspomniałem wcześniej – for comprehensions nie jest ograniczone tylko do list. W Scali istnieje coś takiego jak Option[T], które reprezentuje wartość opcjonalną typu T. Jest to bardzo wygodne rozwiązanie, które pomaga zabezpieczyć się np. przed próbą użycia niezdefiniowanej wartości. Option[T] może zwrócić Some(T), jeśli wartość jest zadeklarowana lub None, jeżeli nie jest. Do zrozumienia listingu nr 2 tyle o Option powinno wystarczyć. Listing nr 2 to przykład użycia for comprehensions nie z listą a właśnie z Option[T]. Mamy klasę Student, który przyjmuje imię i nazwisko oraz dwie zmienne opcjonalne Option[String]. Każda z tych zmiennych akurat ma zdefiniowane wartości, jednak gdybyśmy pobierali te dane np. z API i imię nie zostałoby pomyślnie pobrane, mogłoby mieć wartość None. Następnie z tych dwóch wartości opcjonalnych – imienia i nazwiska, próbujemy utworzyć obiekt Student.
Linie nr 7 oraz 8 przedstawiają pobieranie wartości ze zmiennych imieOpt oraz nazwiskoOpt. W naszym przypadku żadna z wartości nie jest None, a więc w zmiennej imie znajdzie się „Jerzy”, zaś w zmiennej nazwisko znajdzie się „Jurkowski”. Nasze for zwróci więc …(werble)… Nie obiekt Student, lecz Option[Student]. Dla niektórych jest, dla innych nie jest to oczywiste. Dzieje się tak, ponieważ coś zwrócić musimy – a gdyby imię, tak jak wcześniej zakładaliśmy nie zostało zwrócone z hipotetycznego API i przyjęłoby None? Przecież klasa Student przyjmuje dla imienia i nazwiska typ String, nie Option[String]. Nasze None nie nadaje się więc na argument dla studenta. W takim przypadku, gdyby imię było None to nasze for również zwróci None.

Siła for comprehensions

Drzemiąca moc w for comprehensions ujawnia się wraz z lepszym poznaniem paradygmatów języka funkcyjnego oraz gdy przyjdzie potrzeba programować asynchronicznie. Jak już mowa o programowaniu współbieżnym w Scali, to i mowa o typie Future[T]. O Future napiszę innym razem – warto jednak wiedzieć, że dla comprehensions składanie wyników, które dostarczone będą w przyszłości to bułka z masłem.

val foo1 = Future { Thread.sleep(1000); "Paws"}
val foo2 = Future  {Thread.sleep(3000); "Code"}

val blogProgramistyczny = for {
  uno <- foo1
  due <- foo2
} yield uno.concat(due) // PawsCode

Jeżeli chcecie dowiedzieć się więcej o for comprehensions, to zachęcam do przeglądnięcia oficjalnej dokumentacji języka Scala. Pozwolę sobie na autopromocję i dodam, że dokumentację for comprehensions w języku Polskim na oficjalnej stronie Scali, tworzyłem ja 🙂 . Ciekawe przykłady można również znaleźć na stronie alvinalexander.com. Osoby z większą ilością czasu mogą również poczekać aż ponownie popełnię coś o for comprehensions 🙂 . Osobom, które dopiero poznają Scalę i nie są do niej jeszcze przekonani, polecam swój pierwszy wpis: Podróż z Javascript’u do Scali oraz Język programowania Scala.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

Na górę