wtorek, 22 lipca 2008

Języki skryptowe dla JVM - Groovy, Python, Ruby

Maszyna wirtualna Java jest świetną platformą, która udowodniła, że radzi sobie nieźle z nowymi językami. Moim zdaniem na razie liczą się tylko (aż?) 3 języki Jython, JRuby i Groovy. W przyszłości na pewno dołączy do nich Scala (wiki). Są to dojrzałe implementacje, czego nie można powiedzieć o odpowiednikach na platformę .Net.

Cechy wspólne języków
Jython, JRuby, Groovy
  • Mogą być kompilowane do bytecode
  • Moga być interpretowane za równo z konsoli jak i w programach Java z plików lub zwykłego String
  • Mogą wykorzystywać a nawet dziedziczyć po klasach Java
  • Są to języki dynamiczne
  • Pozwalają na przeciążanie operatorów
  • Mapy, listy itp to natywne konstrukcje języka (np. [a: 1, b: 2] to mapa)
  • Pozwalają na pisanie w stylu funkcyjnym
Są to języki z typizacją w stylu "Duck Typing". Troche więcej na ten temat w stopce.

Co ciekawe Jython, JRuby są szybsze niż ich natywne wersje pisane w C. Można porównać testy. W tych testach polecam nie patrzyć na języki spoza JVM, ponieważ procedura testowa nie jest do tego odpowiednia (jak w prawie każdych micro testach). Przy okazji warto zauważyć gdzie jest Scala względem pozostałych trzech i Java.

Jython
Stara sprawdzona implementacja języka Python na JVM i robi to dobrze. Python to jeden z 3 "dozwolonych" języków w Google (obok Java i C). Cecha szczególna - wcięcia zamiast nawiasów klamrowych. Google App Engine obsługuje jak na razie tylko Python.

Znane aplikacje Python: Django, Plone, Zope, Trac (ostatni warty uwagi nawet jeśli nie chcemy pisać w Python).

JRuby
JRuby zapewnia bardzo dobrą kompatybilność z Ruby. Ruby to wcale nie taki młody język, kiedyś znany właściwie tylko w Japonii. Praktycznie całą aktualną popularność zawdzięcza powstaniu webframeworka Ruby on Rails. Sun wpiera implementację JRuby (zatrudnił na stałe kilku jego programistów). Na polskiej Wikipedii znajduje się dobry opis Ruby.

Znane aplikacje Ruby: Ruby on Rails, Basecamp

Zarówno JRuby jak i Jython dostarczają bazowe biblioteki znane z natywnych wersji swoich języków jak i pozwalają korzystać z bibliotek Java.

Groovy
Groovy nie jest portem istniejącego języka, został pomyślany od początku jako "nowa lepsza Java". Większość kodu napisanego w Javie jest jednocześnie poprawnym(choć strasznie przegadanym) kodem Groovy.

Groovy ma najlepszą integrację z Javą:
  • Może być silnie typowany, ale nie musi
  • Klasy Groovy po kompilacji to stare dobre klasy Java, ale nie każdą metodę można wywołać bezpośrednio (w końcu to język dynamiczny, więc pewne metody nie istnieją w trakcie kompilacji)
  • Klasy JDK mają dodatkowe metody, zwykle są one szalenie przydatne
  • Jako jedyny pozwala na dwustronne dziedziczenie (Java -> Groovy i Groovy -> Java) (a odkąd wspiera łączoną kompilację Java + Groovy, możemy mieć cykliczne zależności (dziedziczenie, zawieranie, korzystanie z metod) między klasami Java i Groovy, w Scali też kiedyś pojawi się taka możliwość).
  • Z tych 3 języków tylko Groovy ma możliwość używania adnotacji i generics, a klasy Groovy są poprawnymi klasami Java, czyli możemy sobie oznaczyć klasę Groovy jako "@Entity" i używać dla nich Hibernate - to wykorzystuje np. Seam)
Znane aplikacje w Groovy: Grails (w pewnym sensie (koncepcyjnie) odpowiednik Ruby on Rails, wykorzystuje m.in. Spring i Hibernate)

Gdy się zna Java nie skryptowego języka prostszego do nauki niż Groovy. Nauka może przebiegać w małych krokach - zmieniamy rozszerzenie pliku klasy Java na groovy, a później usuwamy niepotrzebny kod dzięki jakiejś nowo poznanej opcji. Wydaje mi się, że nie pozwala również na klasy wewnętrzne i anonimowe (nie pamiętam teraz). Groovy namiętnie używa pewnej koncepcji nazwanej "builders". Są to takie mini języki tworzące w banalny sposób wszelkie struktury drzewiaste, czyli XML, Swing, SWT, JSP, ANT... Jest to możliwe w innych językach - tu jest jedną z podstawowych koncepcji i ładnie wygląda. Jeśli utknęło się na starej wersji Java np. 1.4.2 to dalej można wykorzystać większość opcji z Groovy (bez np. adnotacji itp.).

Jeszcze troche o typizacji
O typizacji mówi się czasem, że jest słaba lub silna, albo statyczna lub dynamiczna, jest z tym sporo zamieszania i nie mam zamiaru opisywać wszystkiego.
Groovy potrafi udawać statyczną typizacje znaną z Java. Domyślnie tak jak reszta ma dynamiczną typizację, w formie nazywanej "Duck Typing" mi ten termin bardziej by pasował do Ocaml, no ale trudno - tak się przyjeło. W skrócie wszystkie trzy języki pozwalają na przesyłanie czegokolwiek, gdziekolwiek i tylko w trakcie wykonania mogą się poskarżyć, że dany typ jest nie taki bo nie ma funkcji, którą staramy się wywołać.

Przykład z http://onestepback.org/articles/groovy/ducktyping.html
class Duck {
quack() { println "I am a Duck" }
}

class Frog {
quack() { println "I am a Frog" }
}

quackers = [ new Duck(), new Frog() ]
for (q in quackers) {
q.quack()
}
Kod jest poprawny według zasady, że "jeśli coś wygląda jak kaczka i zachowuje się jak kaczka to jest kaczką".

Można powiedzieć, że tak naprawdę nie wywołujemy metody na obiekcie ale przesyłamy komunikat do obiektu z nazwą funkcji i parametrami, a sam obiekt stara się coś z tym zrobić. Dzięki temu możemy obsługiwać funkcje, których nie ma (powiedzmy, że przeciążamy funkcję MethodMissing i tam każemy dla funkcji o sygnaturze hello_xxx wypisywać xxx na ekran). Pozwala to na tworzenie doskonałych bibliotek o bardzo czytelnym kodzie, ale jednocześnie powoduje, że podpowiadanie składni, statyczna analiza kodu, automatyczne wyszukiwanie błędów, czy zwykła nawigacja po kodzie nigdy nie będą tak dobre jak to jest możliwe w Java czy C#. Jednocześnie m.in. z tego powodu języki te zawsze będą wolniejsze niż Java (istnieje dodatkowy narzut na wykonanie każdej funkcji, utrudnia to też pracę JIT).
Tu znów pewnym wyjątkiem jest Groovy, który pozwala na statyczną typizację, tylko nie wiem czy z tego korzysta do optymalizacji. Z drugiej strony jest jedynym z wymienionych języków, który natywnie wspiera Multiple Dispatch, czyli multimethods(dobry krótki przykład) jak to nazywają w Groovy, a to wymaga badania rzeczywistego typu każdego parametru funkcji i ponownie zmniejsza wydajność kodu.

Wracając do Ocaml - tu typizacja jest silna i statyczna (z odgadywaniem typów) mimo to kod często przypomina języki skryptowe. W Ocamlu istnieje coś jak dziedziczenie choć są pewne różnice. Dalej obiekt jest zgodny z jeśli mają te same metody (w uproszczeniu). Ciekawie to działa w praktyce (tak jak na poprzednim przykładzie ale zwróciło by błąd w trakcie kompilacji, gdyby któraś z klas nie zawierała metody quack()). Typy choć na codzień tego się nie dostrzega to dość złożone zagadnienie, dalszych informacji można szukać w google i na wikipedii pod hasłami covariance i contravariance. Przydatne do paru problemów z generics i ze zwykłymi tabelami.

w następnej części opis Scali.
Już teraz specjalnie dla Anke: The Cake Pattern.

Brak komentarzy: