Języki

Erudis - your road to knowledge
Podstawy języka Scala

Scala jest jednym języków programowania nowej fali, mającym pozwolić pisać programy komputerowe łatwiej, przyjemniej i szybciej. Podobnie jak w przypadku Rubyego, Pythona czy Groovyego drogą do osiągnięcia tego celu jest wzbogacenie języka o różnego rodzaju nowe elementy nieznane starej gwardii, czyli C++, Javie czy C#, przy jednoczesnym ograniczeniu potrzeby bardzo skrupulatnego i dosłownego pisania kodu. Kompilator bądź interpreter języka ma być inteligentny i samodzielnie zgadywać intencje programisty, wszędzie tam, gdzie to możliwe.

Scala jest językiem, który ma ułatwić tworzenie oprogramowania komponentowego. Nie jest to proste, gdyż komponent może być zarówno niewielki, implementujący prosty algorytm, jak i bardzo duży, działający w środowisku rozproszonym – na przykład komponent odpowiedzialny za rezerwację biletów lotniczych.

Martin Odersky, pomysłodawca Scali, postawił hipotezę, że potrzebna przy tworzeniu aplikacji komponentowych skalowalność języka może być osiągnięta poprzez połączenie języka zorientowanego obiektowo z funkcyjnym. Siłą języka obiektowego jest łatwość rozwijania złożonego systemu poprzez obiektowy opis problemu, użycie dziedziczenia, interfejsów, klas abstrakcyjnych, polimorfizmu i innych elementów jakie nam daje programowanie obiektowe. Język funkcyjny z kolei pozwala implementować łatwo złożone algorytmy i wygodnie sklejać całość aplikacji z małych części.

Scala jest językiem silnie i statycznie typowanym – każdy obiekt musi mieć przypisany typ. Jeżeli kompilator może sam zgadnąć typ obiektu, to możemy pominąć jego deklarację. W Scali obiektowość jest traktowana z pełną powagą: wszystko jest obiektem, nawet funkcje.

Wielką zaletą Scali, która może przesądzić o jej ewentualnej popularyzacji, jest możliwość wykorzystywania przez nią klas napisanych w Javie, podobnie Java może używać klas wygenerowanych przez kompilator Scali. W przygotowaniu jest także wersja potrafiąca w analogiczny sposób współpracować z platformą .NET.

Zaczniemy od prostego przykładu, w rodzaju tradycyjnego programu Hello World!, tyle, że trochę bardziej złożonego niż zwykle. Popatrzmy zatem na Listing 1 i przeanalizujmy kod.

Listing 1. Program wypisujący na konsoli aktualną datę oraz trzykrotne powitanie na trzy różne sposoby

package pl.erudis.intro;
import java.util.{Date, Locale};
import java.text.DateFormat
import java.text.DateFormat._
object HelloWorld {
   def hello1(msg: String): String = {
      var info: String = "Witaj po raz pierwszy, oto wiadomość dla Ciebie: "   + msg;
      return info;
   }   
   def hello2(msg: String): String = {
      val info = """
       Witaj po raz drugi,
       oto wiadomość dla Ciebie
       """   + msg
      return info
   }
   def hello3(msg: String) = 
      """
      Witaj po raz trzeci,
      oto wiadomość dla Ciebie:
      """   + msg
   def getDateString(): String = { 
      val currentDate = new Date
      val df = getDateInstance(LONG, new Locale("pl"))
      return df format currentDate
   }   
   def main(args: Array[String]) {
      System.out.println("Mamy dziś: " + getDateString)
      println(hello1("witaj!"));
      println(hello2("witaj!"));
      println(hello3("witaj!"));
   }
}

Klasa Scali rozpoczyna się deklaracją pakietu, podobnie jak to się dzieje w Javie. Różnica między Scalą i Javą jest taka, że program nie musi być umieszczony w podkatalogu odpowiadającym nazwie pakietowej. W Javie klasa HelloWorld musiałaby koniecznie znajdować się w podkatalogu pl/erudis/intro. Deklaracja pakietu jest opcjonalna, można ją pominąć, ale oczywiście dobrą praktyką jest używanie pakietów.

Podobnie opcjonalne jest użycie średnika kończącego wyrażenie, o ile każde z poleceń znajduje się w osobnej linii.

Następne kolejne trzy linie kodu importują klasy, których będziemy używać w naszym programie. Składnia polecenia import jest bogatsza niż w języku Java, można, jak widać na listingu, importować listę klas. Warto też zwrócić uwagę, że akurat tutaj importujemy klasy Java a nie Scali – jak wspominaliśmy, w kodzie Scali możemy używać klas napisanych w Javie.

Kod źródłowy wszystkich przykładów jest dostępny razem z czasopismem na płycie oraz na stronie http://erudis.pl/scala/scalaxml.zip.

Aby uruchomić skompilować i uruchomić przykłady należy zainstalować na komputerze Apache Ant (do pobrania ze strony http://ant.apache.org/) i dodać do zmiennej środowiskowej PATH ścieżkę do podkatalogu bin w katalogu instalacyjnym Anta.

Następnie należy rozpakować archiwum z przykładami, w pliku build.properties należy ustawić wartości zmiennej scala.home.property tak, żeby wskazywała na katalog instalacyjny Scali, ponadto można podać klasę, którą chcemy uruchomić ustawiające odpowiednio zmienną run.class. Gdy to wszystko zrobimy wystarczy w katalogu, w którym znajdują się przykłady wykonać z poziomu linii poleceń ant compile, żeby skompilować klasy lub ant run, żeby uruchomić wskazaną przez run.class klasę.

Uruchomienie przykładów dołączonych do artykułu

Bardzo istotną różnicą jest inny niż zazwyczaj używany znak symbolu wieloznacznego. W Scali jest to znak podkreślenia, a nie najczęściej przeznaczona do tego celu gwiazdka, *.

Teraz zaczną się rzeczy naprawdę ciekawe. W kolejnej linii kodu deklarujemy obiekt HelloWorld (tak, tak, obiekt, nie klasę). W Scali możemy tworzyć nie tylko klasy, ale także bezpośrednio obiekty. Obiekt z natury rzeczy jest singletonem – będzie istniał tylko jeden jego egzemplarz. Dzięki temu często używany wzorzec projektowy jest wbudowany w język, a ponadto zbyteczne są pola i metody statyczne, które są z punktu widzenia obiektowości tworem sztucznym. To, co w Javie umieścilibyśmy w metodzie czy polu statycznym w Scali umieszczamy w obiekcie. Proste i eleganckie rozwiązanie.

Zajmijmy się teraz metodą hello1(String). Jest ona zdefiniowana bardzo podobnie jak w Javie czy C# z tą różnicą, że przy deklaracji używamy słowa kluczowego def, a zwracany typ oraz typ przekazywanego metodzie parametru są umieszczone po zmiennej, po dwukropku – dziwna konwencja, ale działa.

Bardziej interesująco wygląda metoda hello2(String). Przede wszystkim tym razem info nie jest zadeklarowane jako zmienna (var) tylko jako stała (val). Sam łańcuch znaków jest zdefiniowany przy pomocy potrójnego cudzysłowu. Notacja ta pozwala definiować obiekty typu String z łańcuchów znaków rozciągających się na wiele linii.

Ostatnia, trzecia wersja metody hello jest najzwięźlejsza. Opuściliśmy deklarację zwracanego przez metodę typu obiektu – kompilator sam go będzie potrafił odgadnąć, opuściliśmy także polecenie return – nawet bez niego kompilator zrozumie nasze intencje, zwracając wartość ostatniego wyrażenie w metodzie.

Metoda getDateString, zwraca datę sformatowaną zgodnie z podanymi ustawieniami lokalizacyjnymi. Metoda getDateInstance i pole LONG są statycznymi składowymi klasy DateFormat, ale nie musimy używać znanej z Javy składni DateFormat.LONG itp. żeby się do nich odwoływać, wystarczy odwołanie do samej metody lub pola. Najdziwniej wygląda ostatnia linijka tej metody, df format currentDate, która jest skróconą formą zapisu df.format(currentDate).

Możliwość stosowania tego rodzaju uproszczenia przy wywołaniu metody i przekazywaniu parametru metodzie budzi dużą ekscytację wśród fanów nowych języków programowania. Warto jednak podchodzić do tego typu sztuczek składniowych z pewną rezerwą. Powodują one, że w bardziej złożonych przypadkach kod staje się mniej czytelny. Tego typu zapis jest niewątpliwie efektowny, ale trudno go nazwać dobrą praktyką programistyczną i przytaczam go tutaj jako ciekawostkę, na którą można się nadziać w czyimś kodzie, a nie coś godnego polecenia.

Na końcu mamy ukoronowanie naszych wysiłków, czyli metodę main(Array[String]), która, tak jak w Javie, jest automatycznie wywoływana podczas użycia obiektu. Wynik działania programu jest dość łatwy do przewidzenia, wygenerowany wydruk znajdziemy na Rysunku 1.

Wynika uruchomienia programu w języku Scala z poziomu edytora Emacs

Rysunek 1. Wynik działania programu HelloWorld (Listing 1)

Warto zwrócić uwagę na to jak wygląda wydrukowany na konsoli łańcuch znaków utworzony przy pomocy wieloliniowej deklaracji używającej 3 cudzysłowów – zachowane jest formatowanie użyte przy definiowaniu tekstu. Wydruk rezultatów działania programu poprzedza polecenie kompilujące klasę Scali (scalac) oraz polecenie uruchamiające skompilowany program (scala)