26 kwietnia 2008

Pierwsze spotkanie ze Scalą - lektura Scala Tutorial for Java programmers

Moje pierwsze spotkanie z językiem programowania Scala rozpocząłem od lektury Scala Tutorial for Java programmers wersja 1.2 z dnia 19 grudnia 2007. Nie miałem jakiegokolwiek pojęcia na temat tego języka poza faktem, że istnieje. Kiedyś coś pisał o nim Wiktor Gworek w Poznajemy nowe języki: Scala, czyli jak wypisać elementy z listy oraz Garść linków do debaty o językach dynamicznych i Scali i myślałem, że w ten sposób poznam Scalę, ale niestety na tym się skończyło. Skoro tyle się o nim mówiło ostatnio (teraz jakby trochę przycichło), to i mnie coś pokusiło, aby się z nim popróbować. Nie żebym narzekał na nadmiar czasu, ale może jest tak, że właśnie go tracę, ponieważ nie korzystam ze Scali (paradoks braku czasu?). Kto wie do czego może przydać mi się ten język. Nie spotkałem nikogo, kto zechciałby mi to wyjaśnić (poza wspomnianymi wpisami Wiktora), więc pokusiłem się o własne spojrzenie na język oczyma kompletnego laika. Może w ten sposób sprowokuję kogoś do wyrażenia swojej opinii o języku Scala.

Scala Tutorial for Java programmers to dokument składający się z 15 stron, w którym zaprezentowano przykłady obrazujące składnię Scali. Dokument czyta się płynnie i daje solidne podstawy do rozpoczęcia programowania w Scali.

Dokument zaczyna się przedstawieniem przykładu typu HelloWorld, w którym poznajemy podobieństwa i różnicę między Scalą a Javą. Cały dokument pisany jest poprzez pryzmat programisty w Javie, więc wiele z przykładów pojawiło się w nim właśnie ze względu na różnice między tymi językami. W HelloWorld poznajemy pierwsze słowa kluczowe Scali - object oraz def. Słowo object określa klasę singleton występującą wyłącznie w jednym egzemplarzu, w której definiujemy metody statyczne, które z kolei nie występują wcale w Scali poza object.

W Scali wymaganymi słowami kluczowymi dla definicji klasy są class lub object.
 jlaskowski@work /cygdrive/c/apps/scala/testy
$ ../bin/scalac HelloWorld.scala
HelloWorld.scala:1: error: expected class or object definition
HelloWorld {
^
one error found
Po poprawnej kompilacji możemy przystąpić do uruchomienia aplikacji i podobnie jak w Javie interpreter Scali domyślnie dołącza katalog bieżący do ścieżki klas.
 jlaskowski@work /cygdrive/c/apps/scala/testy
$ ../bin/scala HelloWorld
Hello, world!
Wbrew przykładowi w dokumencie, nie ma potrzeby dodatkowo definiować parametru -classpath . (na końcu jest kropka określająca katalog bieżący).

Nie mogłem doczekać się informacji o możliwości uruchamiania programów napisanych w Scali z interpreterem Java.
 jlaskowski@work /cygdrive/c/apps/scala/testy
$ java HelloWorld
Exception in thread "main" java.lang.NoClassDefFoundError: scala/ScalaObject
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:620)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:124)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:260)
at java.net.URLClassLoader.access$100(URLClassLoader.java:56)
at java.net.URLClassLoader$1.run(URLClassLoader.java:195)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:268)
at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)
at HelloWorld.main(HelloWorld.scala)
Pierwsza próba niezbyt udana, ale wyjątek kieruje mnie na plk lib/scala-library.jar (bo gdzie indziej mógłbym znaleźć scala.ScalaObject?)
 jlaskowski@work /cygdrive/c/apps/scala/testy
$ java -cp "../lib/scala-library.jar;." HelloWorld
Hello, world!
Działa!

Ciekawostką jest użycie * (gwiazdka) jako poprawnego identyfikatora, co wyklucza jej użycie w deklaracji importu wszystkich klas/interfejsów w pakiecie. Zamiast import java.text.* piszemy import java.text._. To z pewnością będzie sprawiało wiele problemów programistom javy. Dodatkowo za pomocą _ (podkreślenie) importujemy elementy klasy podobnie jak za pomocą import static java.lang.Math.* w Javie.

Podręcznik wspomina o "an infix syntax", gdzie df.format(now) sprowadza się w Scali do df format now. Mniej pisania, ale czy potrzebne, kiedy w użyciu mamy IDE? Pewnie nie, ale już przy pisaniu krótkich programów-skryptów może być pomocne.

Scala jest językiem w pełni obiektowym, gdzie wszystko jest obiektem, włączając w to liczby i funkcje. W Javie typy podstawowe (prymitywy) mają swoje odpowiedniki obiektowe (typy opakowujące), np. int ma Integer, ale o obiektach reprezentujących funkcje mogliśmy jedynie pomyśleć w kontekście użycia mechanizmu prześwietlania (refleksji) z użyciem typu java.lang.reflect.Method.

Jako przykład obiektowości Scali podano niewinnie wyglądający przykład z użyciem operacji arytmetycznych 1 + 2 * 3 / x, co jest równoważne w Scali następującemu ciągowi wywołań funkcji na obiektach reprezentujących liczby 1.+(2.*(3./(x))). Na dokładkę napisano, że operacje arytmetyczne są poprawnymi identyfikatorami w Scali (!) To będzie kolejna pułapka dla programistów Javy, którzy z pewnością będą musieli przejść niezły odwyk, aby przyzwyczaić się do tej konstrukcji. Mnie ścięło z nóg, kiedy to zobaczyłem, ale po chwili nie mogłem oprzeć się wrażeniu, że takie podejście ma coś w sobie interesującego. Mi się podoba (chociaż, gdybym miał uzasadnić taką reakcję, po prostu nie potrafiłbym).

Coś czego nie doświadczymy w programowaniu w Javie to potraktowanie metod jako bytów obiektowych, którymi można manipulować podobnie jak ma to miejsce w przypadku innych klas/interfejsów. Ktokolwiek unikał zajęć programowania funkcjonalnego tym razem się nie wywinie, bo Scala jest cała o obiektach, więc i funkcje przybrały taką postać, co może przypominać elementy programowania funkcyjnego. Można, więc przekazywać funkcje jako parametry wejściowe metod, przypisywać do zmiennych, czy zwrócić z wywołania funkcji (skoro są obiektami to spodziewałbym się, że można na nich wywoływać ich metody - owe ich nie jest mi jeszcze znane). Deklaracja callback: () => unit w Scali oznacza, że mamy do czynienia z funkcją void callback(), gdzie unit w Scali odpowiada void w Javie.

Przykład prezentujący wykorzystanie obiektowe metody korzysta z konstrukcji Thread sleep 1000, która odpowiada Thread.sleep(1000) w Javie. Jak dla mnie notacja Scali jest wciąż novum, do którego nie mogę się przyzwyczaić i śmiem twierdzić, że wprowadza więcej zamieszania niż pożytku. Wszystko jednak przede mną, więc nie ma co narzekać, a raczej próbować zrozumieć, co spowodowało taką decyzję, bo nie sądzę, że jedynym kryterium było skrócenie czasu pisząc program (w tym przypadku, zyskujemy jedynie czas, który poświęcilibyśmy na zamykający nawias).

Jednakże konstrukcja println "Hello, world!" kończy się błędem?!
 jlaskowski@work /cygdrive/c/apps/scala/testy
$ ../bin/scalac HelloWorld.scala
HelloWorld.scala:3: error: ';' expected but string literal found.
println "Hello, world!"
^
one error found
I kolejna niespodzianka - klasy w Scali mogą...przyjmować parametry (!) Pachnie mi tutaj podobieństwem do szablonów w Javie, gdzie można zadeklarować klasę korzystającą z typu T, w postaci public class MojaSparametryzowanaKlasa<T> {}.

Dostęp do zmiennych w postaci metod get oraz set następuje poprzez def re() = real, a to mi przypomina C# (albo po prostu chciałem, aby tak mi przypominało, bo akurat owe metody były ciekawie zaimplementowane w C#). Typ zwracanego obiektu z metody get jest wyliczany dynamicznie i będzie odpowiadał typowi zmiennej, którego dotyczy.

Istnieje pojęcie metod bez parametrów, które wywołanie c.im() sprowadzają do wywołania c.re, kiedy ich definicja w klasie wygląda def re = real.

W Scali typem nadrzędnym wszystkich typów jest Any, jednakże klasa, która nie określa swojego rodzica domyślnie rozszerza typ scala.ScalaObject. I tutaj uwaga do czytelników Scala Tutorial, gdzie napisano o typie scala.Object (5.2 Inheritance and overriding strona 7: When no super-class is specified, as in the Complex example of previous section, scala.Object is implicitly used.). Przesłaniając metodę w klasie pochodnej należy jawnie użyć słowa kluczowego override, np. override def toString() (co tym razem wydłuża czas pisania programu o kilka dodatkowych uderzeń w klawiaturę).

Przez moment chciałem zreferować konstrukcję klasy warunkowej opartej o słowo kluczowe case w Scali, ale tym razem proponuję zajrzeć do tego dokumentu, bo pojęcie tego rodzaju klasy i próba jego relacji przez programistę javy jest nie lada wyzwaniem, którego się dzisiaj nie podejmuję. Są chętni? Przykłady mile widziane.

W dokumencie przedstawiono pojęcie mixin, które określa się za pomocą słowa kluczowego trait podobnie do javowego interface, chociaż mixin może dostarczać implementację.

Słowo kluczowe instanceOf oraz rzutowanie odpowiadają odpowiednio metodom isInstanceOf[] oraz asInstanceOf[].

Na zakończenie dokument przedstawia realizację typów parametryzowanych, które pojawiły się w Javie 5.

Dokument kończy się wskazaniem na kolejny dokument Scala By Example, który zaplanowałem przeczytać...niebawem. Na dzisiaj starczy wrażeń i spróbuję utrzymać naukę Scali poprzez pisanie równolegle aplikacji w niej, dla tych, które będę pisał docelowo w Javie. Będzie początkowo bolało, ale nie widzę innego sposobu.

Pytanie konkursowe: Czy aplikacja napisana w Scali może być uruchomiona przez interpreter Javy?