Nowy wygląd

Jak w temacie.

Chyba nie wyszło powalająco, ale jak przeklikałem się przez kilka innych Joggów to wygląda na to, że konkurencja nie jest zbyt mocna :)

Jolla - witamy w Polsce

Niemal dokładnie miesiąc temu Jolla (czyta się jak się pisze) zaczęła sprzedawać swoje telefony. Mój pre-order przyszedł jakieś dwa tygodnie temu. Myślę, że już czas opisać swoje wrażenia. Tym bardziej, że chyba nikt w naszym kraju tego nie recenzował.

Mobilna telenowela

W 2005 roku Nokia wprowadziła na rynek swój pierwszy Internetowy Tablet (który nie wyglądał jak deska do krojenia z napisem Nokia). Działało to pod kontolą systemu Maemo. Przez następne kilka lat wypuszczali na rynek kolejne model i gdy wybuchła moda na smartfony postanowili dołączyć do wyścigu. W ten sposób w 2009 wyszedł (już jako smartfon) model N900 z zupełnie odnowionym Maemo i z obsługą karty SIM (wcześniej było tylko WiFi). Telefon ten miał wiele ficzerów, które odróżniały go od konkurencji (prawdziwy multitasking, modyfikowalność lepszą niż w Androidzie, ogromną pamięć wewnętrzną i chyba jedyny w historii ekran oporowy, który nie obsysał). W każdym razie zebrał wiele pozytywnych recenzji, dobrze się sprzedał i wydawało się, że Nokia ma mocne podstawy, żeby wejść rosnący rynek smartfonów.

W tym samym czasie Intel pracował nad własnym systemem Moblin przeznaczonym dla swoich Atomów, ale bez większych sukcesów. Postanowili połączyć siły z Nokią i rozpoczęli pracę nad kolejnym systemem - MeeGo. Niestety kosztem było zerwanie zgodności Maemo. Mimo że system wyszedł bardzo dobrze to gwoźdźiem do trumny było oświadczenie Nokii na kilka miesięcy przed premierą pierwszego telefonu (N9), że system porzucają na rzecz Windows Phone. Potem było podobnie jak z N900: dobre recenzje, stosunkowo dobra sprzedaż (jak na urządzenie z systemem skreślonym już w dniu premiery).

Na szczęście MeeGo był w większości otwartym systemem, więc nie trzeba było długo czekać na zebranie szczątków. W pierwszej kolejności kod przejął projekt Mer i do dzisiaj na N9, N950 (developerski model, który nigdy nie pojawił się w sklepach) i N900 można zainstalować rozwijanego przez społeczność potomka MeeGo - Nemo. Potem swojego forka stworzyli Intel z Samsungiem i do dzisiaj rozwijają na tej podstawie Tizena (w przyszłym roku w końcu ma się coś pojawić).

Nie wszystkim w Nokii ten stan rzeczy się podobał. Krótko po premierze N9 część osób pracujących wcześniej przy Maemo i MeeGo zwinęła manatki i założyli własną firmę - Jolla. I tak oto doczekaliśmy ideologicznego potomka wcześniejszych osiągnięć Nokii, którego mam teraz w ręce.

Sprzęt

Pierwsza niespodzianka - mimo że przeczytałem wcześniej specyfikację jakieś milion razy gdzieś umknął mi fakt, że telefon przyjmuje tylko micro-SIM. No, ale cóż, nożyczki w domu są, więc jakoś sobie z tym poradziłem. Wizualnie jak widać prezentuje się ładnie. Jakość wykoniania jak na Finów przystało na wysokim poziomie. Poza tym wszyscy mylą ją z Nokią (chyba nie powinienem być zaskoczony?). Jeśli chodzi bebechy to mamy tu dwa rdzenie Krait 200 taktowane na 1.4 GHz, Adreno 305 i 1 GB RAM (plus 512 MB w swapie, są w ogóle na rynku jakieś inne telefony, które mają fabrycznie swap?). Wbudowany Flash ma 16GB (co i tak wypada blado w porównaniu z N900, który miał zawrotne 32 GB) z czego do użytku zostaje nam jakieś 10 GB. Fani gigaherców i gigabajtów bedą zawiedzeni, ale do normalnego użytkowania spokojnie wystarczy.

Jest tylko jeden zgrzyt. Ekran ma 4.5 cala przez co telefon jest ciężki w obsłudze jedną ręką. Szkoda że twórcy poszli za modą produkowania patelniofonów. Jeśli chodzi o aparat to się nie wypowiadam, bo się na tym zwyczajnie nie znam. Nie potrzebowałem niczego lepszego odkąd aparaty w telefonach były w stanie zrobić zdjęcia w rozdzielczości wyższej niz 640x480.

Unikalnym elementem tego telefonu jest ustrojstwo nazwane The Other Half. Chodzi po prostu o tę klapkę z tyłu. W przeciwieństwie do normalnej klapki może się aktywnie komunikować z telefonem. Potencjalnie tworzy to możliwość tworzenia w przyszłości różnej maści inteligentych klapek (podobno jest możliwe nawet stworzenie doczepianej klawiatury, żeby upodobnić telefon do N900). Ja z racji pre-orderu dostałem dodatkowo pomarańczową (oj, przepraszam, Poppy Red) z napisem "The First One". O piekielności tego cholerstwa za chwilę...

System

Telefon jest wyposażony w system Sailfish OS. Jest to połączenie projektu Mer z kodem wyprodukowanym w firmie Jolla. To drugi nowy system mobilny, który pojawił się w tym roku (albo trzeci jeśli liczyć Blackberry 10). A, właśnie, Alcatel One Touch Fire też mam (kosztował mnie 10 razy mniej niż Jolla, bo... z winy T-Mobile/Alcatela/Mozilli/Iluminatów miał bardzo słaby start i ludzie masowo się ich pozbywają).

Po odpaleniu telefonu system oczywiście prowadzi nas przez wstępną konfigurację. Nie różni się to zbytnio od każdego innego smartfonu, bo w tej materii raczej ciężko wymyślić coś nowego (przynajmniej dopóki telefony nie zaczną nam czytać w myślach). Zabawa zaczyna się potem. Na start dostajemy tutorial jak obsłużyć UI. Powiedzmy sobie szczerze, gdybyśmy dali osobie, która miała do tej pory styczność tylko z feature phonami jakiś model z Androidem to szybko by się połapała co trzeba kliknąć. Tutaj klikania jest niewiele. System jest w dużej mierze oparte na gestach. I tak smyrnięcie od góry zamyka aplikację, smyrnięcie z boku minimalizuje, od dołu przechodzimy do Event Screenu, gdzie znajdują się wszytkie powiadomienia. Wszystko to jest fajne prócz dziwacznego menu aplikacji, które... zobacz sobie filmiki w necie, bo ciężko to opisać.

Multitasking działa podobnie jak w poprzednich finlandzkich systemach. Aplikacja działa tak długo, aż jej nie zamkniemy. Będąc w tle może robić cokolwiek, zupełnie jak aplikacje w normalnym PC. Nie ma żadnych stanów uśpienia czy innych cudów, chociaż apki mają dostęp do informacji czy są teraz wyświetlane na ekranie czy nie.

Na ekran główny składają się trzy segmenty (albo więcej jak pointalujemy już trochę aplikacji). Pierwszy to Lock Screen. Życie przyzwyczaiło mnie, że Lock Screen jest tymczasowym widokiem. Przeciągamy, co mamy przeciągnąć i telefon jest odblokowany. Tutaj ten ekran jest cały czas obecny. Mamy tu wyświetlone podstawowe informacje o stanie telefonu i czas (chociaż jest sporo wolnego miejsca i mam nadzieję, że twórcy w przyszłości to wykorzystają). Nie ma nic klikalnego, więc nawet jak przez spodnie wejdziemy w ten ekran (tradycyjnie przez wciśnięcie przycisku blokady lub przez podwójne tapnięcie) to bez wykonania jakiegoś gestu nic nie zepsujemy.

Kolejny ekran, poniżej zawiera grida wszystkich otwartych aplikacji. Do tego na dole są cztery sloty na częściej używane aplikacje (domyślnie telefon, przeglądarka itp.). Tu jest kolejny unikalny element, którego nie znajdziemy w innym systemie. Każda aplikacja może posiadać okładkę (ale nie musi, wtedy widzimy po prostu miniaturkę). Na okładce może się znajdować dowolna treść (np. tytuł aktualnie słuchanej piosenki) i do tego maksymalnie dwie akcje, które możemy aktywować smyrnięciem okładki w lewo lub w prawo.

Ostatni ekran to oczywiście lista aplikacji, w razie większej ich ilości pojawiają się kolejne ekrany. Tu nie ma nic odkrywczego, apki można przestawiać i usuwać jak w każdym innym smartfonie (na ten moment nie ma jeszcze folderów).

Do tej pory sam system ani razu się nie przyciął nawet przy dużej ilości otwartych aplikacji. Wielu rzeczy jeszcze brakuje, ale to co jest działa bez jakichś większych bugów (w przeciwieństwie do Firefox OS 1.0). Wizualnie jest bardzo kolorowo i radośnie (czasem do przesady), szczególnie po podpięciu pre-orderowej klapki (ten dzwonek jest koszmarny). Na szczęście można ustawić własny motyw (właściwie to tylko tapetę i dzwonki, ale przy tym minimaliźmie to wystarczy).

Telefonia

Aplikacja telefonu jest równie badziewna (albo nawet gorsza) jak w każdym innym smartfonie, w końcu one nie służą do dzwonienia. Podobnie jak za czasów Maemo całą komunikację od SMSów po Jabbera i fejsika ogarnia jedna aplikacja.

Przeglądarka

Kiedy w Firefox OS zobaczyłem przeglądarkę myślałem, że bardziej ubogo nie da się zrobić. Jednak się da. Do samego silnika nie można się przyczepić, mamy tu najświeższego Gecko 26 i mimo, że większość telefonów bazuje na WebKicie to mozillowy produkt też jest dobry. Problem w tym, że obsługa tego ustrojstwa jest niezwykle uciążliwa, poza tym nie ma trybu poziomego (większość aplikacji go nie obsługuje, ale w przeglądarce to jest niemal konieczność). Na dokładkę w ustawieniach mamy dostępne słownie dwie opcje.

Multimedia

Telefon ma domyślnie wrzucone jakieś zdjęcia i filmiki. Nie mogę powiedzieć, żeby były zbyt ciekawe. Już chyba wolę obecne w normalnych telefonach filmiki propagandowe niż cieszących się ludzi. Jeśli chodzi o samą aplikację galerii to jest ok. Gorzej z odtwarzaczem muzyki. Większość mojej muzyki nie zawiera w sobie metadanych, po prostu wszystko leży w katalogach poukładane według sposobu pozyskania, nastroju, kategorii, czasem nawet wedle albumu albo wykonawcy. Brak możliwości odtwarzania pojedyńczych folderów to spory problem. Do tego z jakiegoś powodu nie jest w stanie odtworzyć niektórych moich piosenek.

Sklep

Jeśli nie lubisz kiedy domyślnie masz w systemie zainstalowane pierdyliard aplikacji do fejsików, twitterów i innego gówna, którego nie używasz to polubisz Sailfish OS. Oni przegięli w drugą stronę. Tu na starcie prawie nic nie ma, po wejściu do sklepu pierwsze co się rzuca w oczy to banner Jolla Essentials, gdzie można doinstalować takie pierdoły jak nawigacja, kalkulator albo budzik (serio!). Wszystkich aplikacji w tym momencie jest na oko kilkadziesiąt (sic!). Nawet Firefox OS w dniu premiery miał więcej (chociaż to nie dziwi). Jeśli jesteś typem, który potrzebuje osobnej aplikacji do podcierania dupy to możesz się zawieść, chociaż...

Android Support

Rzeczą, którą Jolla się chwaliła już od pierwszych zmianek o tym telefonie była możliwość odpalania aplikacji z Androida. Konkretnie użyli rozwiązania Alien Dalvik firmy Myriad Group. Szczerze mówiąc po latach doświadczeń z różnymi emulatorami i nie-emulatorami (wine) byłem nastawiony raczej na to, że tak z 10% aplikacji w ogóle odpali. Jest inaczej, jak do tej pory wszystko działa, jedynie czasem są zgrzyty w podczepieniu poszczególnych API Androida do API Jolli (tydzień temu nie działał aparat, ostatnia aktualizacja to naprawiła), a czasem w samej Jolli (latarka nie działa do teraz, nawet wykorzystując natywne API). Zaraz po tym odkryciu zainstalowałem Firefox for Android, bo tego domyślnego gówna nie da się używać.

Developer mode

W ustawieniach systemu możemy aktywować Developer Mode. Po tym pojawia się aplikacja terminala, a ponadto można też włączyć SSH. W konsoli Jolli poczułem się bardzo swojsko. Nie ma tutaj takiej parodii Linuksa jak w Androidzie. Sam system strukturą przypomina każdą normalną dystrubucję Linuksa. W przeciwieństwie do Androida jest większość podstawowych poleceń systemu i nie są one w żaden sposób okrojone. W sam raz, żeby greptować kompile jak normalny, biały człowiek. Podobnie było w Maemo i MeeGo. Tutaj dla odmiany paczki są w RPMach, a menadżer pakietów to PackageKit. Dostęp do roota oczywiście też nie jest w żaden sposób utrudniony (polecenie su-devel). Hackability na najwyższym poziomie.

Development

Postawienie środowiska nie jest gorsze niż w Androidzie (oczywiście nic nie pobije Firefoksa, gdzie można stworzyć działającą aplikację bez żadnych narzędzi programistycznych). Trzeba zainstalować gigabajty jakichś śmieci i używać domyślnego IDE dopóki nie rozpracuje się całego toolchainu. Jest jedna dziwna rzecz, instalka wrzuciła mi dwa obrazy do VirtualBoksa. Jeden to emulator (nie, to nie jest ta dziwna rzecz), a drugi to... coś (to jest ta dziwna rzecz). Z tego co zaobserwowałem to do tej drugiej maszyny wrzucane są pliki projektu po SSH i tam przebiega proces budowania. Wygląda to dla mnie jak chamskie obejście problemu z kompatybilnością narzędzi z systemem. No dobra, ale mamy dzisiaj gigabajty RAMu, więc nie ma się czym przejmować.

Aplikacje pod Sailfish OS bazują na QT5 + QT Quick + Sailfish Silicia. Teoretycznie projekt jest w C++, ale tak naprawdę to tylko startuje całą maszynkę wykonującą QML. Wedle wiki to dialekt EcmaScriptu, ale jak dla mnie to niezbyt podobne do EcmaScript, za to całkiem podobne czegoś w czym nie chciałbym pisać. Z drugiej strony można to spiąć JavaScriptem, więc jeśli masz na ścianie przywieszone prawo Atwooda i uważasz, że cały świat powinien być oparty na Node.js to możesz stworzyć aplikację niemal całkowicie w JS. W czystym C++ prawdopodobnie też da się pisać.

Dokumentacja całości jest raczej słaba. O ile o QT coś jest (chociaż nie jest to zbyt dobrze udokumentowane) to jeśli chodzi o Silicia to dokumentacja wygląda jakby ktoś ją wygenerował i nie spojrzał czy wszystko się kupy trzyma. O jakichś tutorialach w necie można pomarzyć. Wątpliwości rozwiązywałem przeglądając kod aplikacji stworzonych przez Jollę (teoretycznie zamknięte, ale pliki QML leżą w niezmienionej postaci w telefonie).

Poza tym Jolla przebąkiwała coś o wsparciu aplikacji z Firefox OS (silnik już mają).

Are we fast yet czyli Java i Firefox OS

Jak pisałem ostatnio pracuję nad emulatorem J2ME na Firefox OS. Niestety pierwsze rozczarowanie przyszło, gdy aplikacja wylądowała po raz pierwszy na żywym sprzęcie, a dokładnie na Keonie (niewiele lepsze gówno niż Alcatel One Touch Fire). Wydajność była słaba, w męczonym przeze mnie Raymanie na oko wyciągało mniej niż 5 FPS (nie było wtedy jeszcze zaimplementowanego licznika klatek). W czasach gdy chodziłem do podstawówki i była moda na emulatory nie było najmniejszego problemu z emulowaniem gier na niemal martwą platformę, bo mój komputer (Pentium II) był kilkanaście razy wydajniejszy od SNESa. Problem w tym, że tu mamy telefony z podzespołami, które mogły robić wrażenie w 2010 roku podczas, gdy topowe telefony wspierające natywnie emulowaną platformę były niewiele słabsze, a sama platforma... wciąż żyje.

...ale to nie znaczy, że można się poddawać

Sytuacja wymagała ostrej optymalizacji. Czy się udało? Rzucę teraz wykresami...



Benche wzięte z Computer Language Benchmarks Game. Gwoli ścisłości ten niebieski wykres to Doppio, najbardziej chyba dojrzała maszyna wirtualna Javy napisana w JS. Zółty (może trochę słabo widoczny :) ) to wersja 0.7 mojego emulatora sprzed kilku dni. Chciałem dorzucić do porównania wcześniejsze wersje, ale 0.4 nie dała rady odpalić benchmarków, więc ostatecznie porównuję z 0.5 i 0.6. Ten wpis ma na celu opisanie w jaki sposób emulacja przyspieszyła stukrotnie w ciągu miesiąca.

Jak to działało (i po części działa do teraz)

Wersja 0.5 nie różniła się zbytnio sposobem wykonywania kodu od poprzednich. Metoda Javy opisana jest przez opkody, które tłumaczyłem do funkcji JS (jeden opkod, jedna funkcja). Powstawała z tego tablica funkcji, które wykonywałem po kolei. Pojedyńcza funkcja dostawała jako jedyny argument obiekt opisujący kontekst wykonywania i starała się robić to co prawdziwa maszyna Javy, czyli mogła np. zdjąć dwie liczby ze stosu i wrzucić na niego wynik ich dodania, wywołać metodę z innego obiektu albo nakazać egzekutorowi zrobić skok do innego indeksu w tablicy. Tak to działa w sumie do dzisiaj choć już nie zawsze i z kilkoma zmianami...

Scalanie kodu

Pierwsza i dająca największego kopa zmiana to podział tych funkcji, które dostajemy w wynikowej tablicy na dwa typy. Generatory (każdy opkod ma swój generator, który na podstawie otrzymanych danych zwraca odpowiednią dopasowną funkcję) mogą zwrócić albo po staremu funkcję (ile razy już użyłem tego słowa w tym akapicie?) albo stringa z kodem. Co to daje? Że mogę sąsiadujące ze sobą kawałki kodu scalić w jeden (o ile w miejsce pomiędzy nimi nie jest zagrożone skokiem). W ostatnim etapie ze wszystkich kawałków kodu tworzę funkcje i ostatecznie wszystko jest po staremu, poza tym, że mam mniej funkcji w tablicy niż wcześniej. Co ciekawe ten prosty trik był na tyle skuteczny, że nawet po przerobieniu kilkunastu najczęściej używanych opkodów na nową konwencję wzrost wydajności był wyraźny. Ostatecznie doszło do tego, że metoda, która wcześniej składała ze 199 kawałków zeszła do ~25. Koszt przejścia egzekutora z jednego kawałka na kolejny jest spory i... właściwie większość tego spadku między czerwonym, a pomarańczowym słupkiem wynikła właśnie z tego.

Redukcja operacji na stosie

Podglądając co z tego scalania się zrodziło szybko dostrzegłem sytacje tego typu:
context.stack.push(context.locals[0]);
context.stack.push(1);
var a = context.stack.pop();
var b = context.stack.pop();
context.stack.push(a + b);

Jak łatwo się domyśleć wrzucanie czegoś do tablicy tylko po to, żeby to za chwilę odczytać jest bezsensowne i kosztowne obliczeniowo. Wprowadziłem więc mechanizm, który te zjawisko redukuje (niemal do zera). Dla każdego popa szuka najbliższego pusha i wiąże je zmienną pomocniczą. Po jego użyciu powstaje taki kod:
var tmp1 = context.locals[0];
var tmp2 = 1;
var a = tmp2;
var b = tmp1;
context.stack.push(a + b);

Teraz dla odmiany mamy nadmiarowe zmienne, ale to nie jest problemem, bo wedle moich testów silnik sam takie rzeczy optymalizują, więc mogę sobie pozwolić na ping-pong między zmiennymi bez obaw o wydajność. Ta optymalizacja zmniejszyła czas wykonywania o jakieś 20-30%.

Kompilacja do metody natywnej

Kolejna rzecz jaką zauważyłem to, że niektóre (bardzo niewiele, ale byłem nastawiony, że w przysłości pojawi się ich więcej) metody po scaleniu redukują się do tablicy z pojedyńczą funkcją. W takich sytuacjach (o ile spełnione są pewne warunki) sprowadzam metodę do całkowicie natywnej, JavaScriptowej postaci bez egzekutora i innych narzutów. Wzrost wydajności praktycznie żaden przy odsetku jaki stanowiły wówczas takie metody, ale potraktowałem to jako inwestycję w przyszłość (spoiler: będzie ich więcej).

Bezpieczne metody

Ponieważ niektóre rzeczy w JS dzieją się asynchronicznie wołana metoda mogła nakazać zwinąć ten mój Javowy pseudowątek i rozwinąć, gdy operacja się zakończy (np. ładowanie obrazków). To sprawiało, że nie mogłem pod żadnym pozorem scalać wywołań metod z resztą kodu. Jak łatwo się domyśleć wywołań innych metod w Javie jest całkiem sporo. Dlatego wprowadziłem coś takiego jak bezpieczne metody (i analogicznie niebezpieczne). Bezpieczne metody domyślnie są wszystkie z bibioteki standardowej oprócz kilku, które oznaczyłem jako niebezpieczne (czyli wspomniane wcześniej ładowanie obrazków albo usypianie wątków). Wszystkie metody z emulowanej aplikacji są domyślnie niebezpieczne. Potem informacja o "bezpieczności" metody propaguje dalej i z czasem też niektóre metody z aplikacji uznawane są za bezpieczne. Oczywiście wywołanie bezpiecznej metody może być bez problemu scalane z okolicznym kodem. Czyli kolejna redukcja :)

Double i Long

Do tej pory Double i Long były specjalnymi typami (bo sama maszyna wirtualna Javy traktuje je specjalnie) tworzonymi z konstruktorów. W momencie, gdy profiler wskazał, że ok. 10% działania programu to konstrukcja Longa postanowiłem coś z tym zrobić. Zamiast używać konstruktorów przerzuciłem się na duck-typing. Jeśli coś wygląda jak Long (albo Double) to nim jest. Tworzę anonimowe obiekty w miejscu, a ponadto jako, że są immutable to 0 i 1 trzymam w stałych, żeby nie tworzyć ich za każdym razem. Podane wyżej benchmarki jako, że intensywnie korzystają z Doubli bardzo polubiły tę optymalizację.

Pętle i instrukcje warunkowe

W tym momencie już tylko skoki psuły mi wydajność. Oczywiście natywny for jest dużo wydajniejszy niż egzekutor odtwarzający tę logikę. Temat był dość ciężki, w pewnym momencie miałem jakieś 200 linii heurystyki ogarniającej różne rodzaje pętli i ifów. I tak nie pokrywały wszystkiego, a poza tym alternatywy i koniunkcje logiczne też tworzyły skoki. No i oczywiście goto (na szczęście tego mało kto używa). Po długich przemyśleniach i poszukiwaniach znalazłem prosty, skuteczny i ostro porąbany sposób na zlikwidowanie większości skoków w kodzie. Ale do rzeczy. Jeśli jesteś zdrowym na umyśle programistą JS to pewnie nie słyszałeś o labelach, a tym bardziej ich nie używałeś. Okazało się, że dzięki nim można prosto odtworzyć większość pętli i nawet instrukcji warunkowych. Przykład pętli:
for (i = 0; i < l; i++) {
  ...
}

Przykład tej samej pętli zapisanej na labelach:
i = 0;
label1: do {
  if (i => l) break label1;
  ...
  i++
  continue label1;
} while (true);

Moja maszynka rozpisałaby to jeszcze bardziej absurdalnie (pewnie by było więcej pętli). Co najlepsze bez względu na ilość zagnieżdżeń działa to podobnie szybko co ładnie zapisany for. Tworzenie tych pętli ogarniają dwa ify w kodzie, a efekty są dość... powalające. Jeśli się zastanawiałeś, dlaczego żółty wykres na początku wpisu był taki krótki to jest odpowiedź. To chyba druga najbardziej skuteczna optymalizacja jaką zastosowałem. W każdym razie najbardziej trikowa.

I inne

Poczyniłem też dużo mniejszych optymalizacji, od czasu do czasu poprawiam wydajność pojedyńczych opkodów, nie mówiąc o tym, że niektóre generatory są jeszcze w starej "konwencji" i nie zwracają kawałków kodu tylko funkcje (pomimo, że mogą). Jak tylko natrafię na takie staram się poprawić, ale to wszystko stopniowo, nie chce mi się czytać ponad tysiąca linii kodu szukając słabych punktów. Poza tym dobra rada, jeśli liczy się wydajność nie używaj arguments. Jakiekolwiek użycie tego w kodzie wyłącza niektóre optymalizacje i wydajność w Firefoksie leci na łeb na szyje (w V8 trochę mniej, ale też spada).

Co dalej?

  • Zoptymalizowanie wywołań metod - to dość kluczowy element programu i wydaje mi się, że sporo można jeszcze poprawić.
  • TypedArray - oglądałem benchmark spectralnorm napisany w JavaScripcie, były tam użyte TypedArraye. Nie widzę przeciwskazań, żeby użyć ich u siebie, bo na szczęście Java jest typowana.
  • Natywne wyjątki - mimo że to dość proste do wykodzenia i oczywiste to jeszcze tego nie zrobiłem :)
  • Workery - właściwie poczyniłem już pewne kroki w tym temacie. Zrobiłem jednego workera trzymającego całą maszynę wirtualną i komunikacja z DOMem odbywała się przez przesyłanie odpowiednich komunikatów. Wydajność trochę wzrosła, niestety większość telefonów z Firefoksem jest jednordzeniowa, więc podejrzewam, że tam to może dać tylko niepotrzebny narzut.
  • Switch - być może wykorzystam pomysł mina86 z komentarzy pod poprzednim wpisem, żeby dobić niechcące się kompletnie scalić przypadki
  • I co mi tam jeszcze przyjdzie do głowy. Na razie wydajność jest zadowalająca, więc nie spieszę się z optymalizacją. Raczej skupiam się teraz na zgodności z J2ME.

Java Mobile w JavaScripcie

Tak się złożyło, że pod wpływem ostatnio panującej mody na Firefox OS od miesiąca klepię taki swój mały projekt. Mianowicie chodzi o emulator J2ME działający pod Firefox OS. Na razie efekt jest taki:

Można sobie pograć w Raymana. Prawda że fajne? Celem tego wpisu jest przedstawienie jakim cudem to działa.

Ale o czym ty w ogóle piszesz?

Tytułem wstępu, czym jest J2ME (a raczej Java ME, bo Oracle już dawno wywalił te dwójki z nazw swoich produktów, ale ja wolę taką nazwę), bo pewnie nie każdy o tym słyszał (a raczej słyszał, ale pod nieco inną nazwą). Otóż w dawnym czasach, gdy superkomputery miały moc obliczeniową współczesnych spinaczy do papieru, a przeciętną komórką można było zabić człowieka jedyne aplikacje, których można użyć w telefonie były te wbudowane w system. Prawda, że taka wizja nie wygląda zbyt ciekawie, kiedy dzisiaj można za jednym zamachem zrobić aplikację obejmującą niemal połowę rynku?

Była wtedy taka nieistniejąca już firma o nazwie Sun ([*]), która postanowiła rozwiązać ten problem i przy okazji zarobić trochę na opłatach licencyjnych. Mieli wtedy coś, co bardzo ułatwiło im osiągniecie tego celu, mieli Javę. Jedyne co musieli zrobić to upchać tę kobyłę do liczonej wtedy w kilobajtach pamięci telefonu komórkowego i zainteresować producentów.

Pierwsza wersja J2ME pojawiła w 1999. Układ był prosty, producenci telefonów płacą Sunowi, a ten daje pozwolenie i pomoc w zaimplementowaniu tego standardu w swoich telefonach. Pomysł wypalił, bo w ciągu kilku kolejny lat pojawiły się pierwsze urządzenia z implementacją J2ME, a wkrótce już trudno było kupić telefon, który by nie miał w sobie Javy. Aplikacje oparte na Javie w tamtych czasach po prostu opanowały komórki i ostatnią stronę Telemagazynu ("ostatnia strona Telemagazynu" to ówczesny AppStore).

Jednak nic nie trwa wiecznie, współcześnie o aplikacjach na J2ME słyszy się raczej w podaniach ludowych niż widzi na co dzień. Jak nietrudno się domyśleć to sprawka Androida i iOS, które opanowały rynek w ostatnich latach. Oczywiście dalej wielu ludzi ma featurephony i istnieją firmy, które tworzą aplikacje na J2ME, ale dzisiaj to już margines.

Historia historią, ale to jest blog techniczny

Generalnie w standardzie J2ME ważne są dwie główne specyfikacje: CLDC i MIDP. Ta pierwsza to najogólniej mówiąc skrójka J2SE (to ta, która ciągle woła o aktualizację). Oficjalny dokument odsyła nas do poczytania specyfikacji JVM 1.3 (Wirtualna Maszyna Javy, rdzeń tych wszystkich "jotek"), mówi co z tego standardu odrzucić, a następnie opisuje swoją bibliotekę standardową, która jest całkowicie kompatybilna z tą z "dużej" Javy, ale klas jest znacznie mniej, a te które zostały mają mniej metod niż ich starsi bracia (taki najbardziej dyrastyczny przykład: CLDC vs. J2SE). Wszystko po to, żeby gotowe implementacje mogły zajmować jak najmniej pamięci. Samo CLDC do tworzenia aplikacji się nie nadaje, bo jedynym sposobem na kontakt ze światem wewnętrznym jest standardowe wyjście. W zamierzeniu miała służyć za podstawę dla kolejnych specyfikacji dla różnych urządzeń. W przypadku komórek taką specyfikacją jest MIDP. Rozszerza CLDC o kolejne klasy, które pozwalają na komunikację aplikacji z urządzeniem czyli m.in. GUI, dźwięk, zbiornik na dane. Oprócz tych dwóch z biegiem latach powstało kilka innych specyfikacji, które są opcjonalne przy certyfikacji. Oprócz tego niektórzy producenci wprowadzili kilka własnych klas. Przez to mimo wszystko jakaś tam fragmentacja jednak istniała i niektórych aplikacji nie można było odpalić na każdym scertyfikowanym telefonie. Poza tym w 2009 pojawiła wersja 3.0 specyfikacji MIDP, ale do dzisiaj prawie nikt się nią nie zainteresował.

Uważny czytelnik po tym krótkim opisie domyśli się, że tak naprawdę każda implementacja zgodna z J2SE (a nawet ta karykatura na Androidzie) jest też zgodna z CLDC. Tak, właśnie dlatego każdy normalny człowiek pisze emulatory J2ME w Javie, bo do zaimplementowania pozostaje tak naprawdę tylko MIDP. A teraz zobaczmy, jak to wygląda w JavaScripcie...

Podstawy

Mimo że nie mam ułatwienia w postaci JVM (przynajmniej wedle mojej wiedzy czegoś takiego na Firefox OS nie ma) to postarałem się jak najbardziej oprzeć się na tym co jest w JS. Obiektowość jest (co prawda przez prototypowanie, ale to nie problem odpowiednio ją obudwać), zarządzanie pamięcią jest, wyjątki są... i to chyba tyle. Wszystkie klasy Javy trzymam przestrzeni nazw javaRoot, do tego dodaję znak dolara do nazw klas i pakietów (czyli np. java.lang.String mapuje się u mnie jako javaRoot.$java.$lang.$String). Z metodami jest nieco gorzej, bo w Javie np. metoda X, która przyjmuje jako argument Stringa to zupełnie inna metoda niż taka o tej samej nazwie i w tej samej klasie, ale przyjmująca jako argument Integera. Rozwiązałem to generując nazwy metod opierając się też na typach argumentów i zwracanej zmiennej. Przez to teraz istnieją takie potworki jak $setCommandListener$Ljavax_microedition_lcdui_CommandListener_$V. Zmniejsza to czytelność kodu, ale nic lepszego mi nie przyszło do głowy.

JAR

Paczką w której przenoszą się aplikacje J2ME podobnie jak wszystkie inne aplikacje w Javie jest format JAR. Jak pewnie wiecie JAR jest zwykłym ZIPem, w którym pliki są poukładane wedle pewnej logiki. Do otwarcia takiej paczki użyłem gotowego rozwiązania, które zwie się zip.js. Ważny fakt to że aplikacja może chcieć odczytać każdy plik z paczki. Żeby ułatwić sobie życie zawartość wszystkich plików trzymam w pamięci. Te JARy nie są duże, a najsłabszy znany telefon z Firefox OS ma mieć 512 MB RAM, więc chyba mogę sobie pozwolić na takie ułatwienie.

Wczytywanie klas

Kolejnym etapem obrobienia takiej aplikacji to wczytanie wszystkich klas. To kolejne ułatwienie z mojej strony, bo maszyny wirtualne zazwyczaj doczytują je dopiero, gdy są potrzebne, ale póki co takie rozwiązanie jest wystarczająco dobre. Jeden plik .class zawiera opis jednej klasy. Mimo że w pojedyńczym pliku z kodem źródłowym można zdefiniować kilka klas to kompilator stworzy nam kilka plików .class.

Opiszę ogólnie z czego się taki plik składa. Zaraz po nagłówkach (0xCAFEBABE) i tego typu pierdołach znajduje się dość ciekawa struktura, która jest podstawą dla prawie wszystkiego, co jest opisane w dalszej części pliku. Nazywa się to Constant Pool, jest to po prostu tablicą ze stałymi. Najbardziej podstawowym jest UTF8 czyli jak łatwo się domyśleć ciąg znaków. Nie mylić z Javowym typem String, takie stałe są osobnym typem i odwołują się po prostu do stałych UTF8. Poza tym jak łatwo się domyśleć są Inty, Floaty i parę innych typów. Z bardziej złożonych mamy klasy, pola, metody itd. Z powodu tego, że różne stałe odwołują się do siebie nawzajem po wczytaniu ich wszystkich przerabiam je na bardziej złożone struktury, żeby nie musieć potem skakać po tej całej tablicy.

Następnie pozostaje wczytać całą strukturę klasy. Tak jak mówiłem wszystko się odwołuje do Constant Pool czyli np. nazwa klasy jest podana jako indeks stałej typu ClassInfo. W dalszej części pliku są podane podstawowe informacje o klasie, pola, metody, jakie dana metoda łapie wyjątki i co najważniejsze - bytecode.

Kod bajtowy

To jest najważniejszy element całej tej zabawy, bo tak naprawdę głównym zadaniem maszyny wirtualnej jest wykonywanie tego kodu. Każda metoda ma swój, w zapisie symbolicznym przypomina trochę instrukcje Assemblera, tylko że zamiast operować na pamięci, operuje na obiektach, a zamiast rejestrów ma stos i zmienne lokalne. Po krótce wyjaśnię na jakiej zasadzie działa to w praktyce:

0 iload_1
1 ldc #59
3 iadd
4 istore_3
5 ireturn

Przede wszystkim każda metoda na początku wykonywania kodu w zmiennej lokalnej nr. 0 ma obiekt na którym ta metoda jest wołana (z wyjątkiem metod statycznych). W kolejnych slotach umieszczane są argumenty metody. Załóżmy, że podana tutaj metoda dostała jako argument liczbę 5. Pierwsza instrukcja wrzuca na stos zmienną lokalną nr. 1 (czyli liczbę 5), druga natomiast z wrzuca stałą z indeksem 59 (umówmy się, że leży tam liczba 9). Warto też zwrócić uwagę, że zajmuje dwa bajty (jeden, żeby zapisać kod instrukcji i drugi, żeby zapisać liczbę 59). Instrukcja iadd ściąga ze stosu dwie liczby, dodaje je ze sobą i wrzuca z powrotem wynik. Ostatnia instrukcja ściąga go ze stosu i zwraca jako wynik metody. Czyli nasza metoda zwraca liczbę 14. Jak pewnie się domyślacie te literki "i" na początku każdej operacji oznaczają, że tyczą się one tylko liczb całkowitych. Gdybyśmy chcieli zwrócić np. referencję do jakiegoś obiektu, użylibyśmy areturn.

No dobra, wszystko jasne tylko jak to wykonywać w JS? Pierwsze moje podejście polegało na generacji kodu JS i tworzenie metod konstruktorem Function. Czyli np. z iload_1 generował stack.push(locals[1]). Niestety pomysł upadł, gdy tylko pojawiły się instrukcje skoku. Brak goto w JS (komentarze w stylu "goto to zło" będą usuwane) kompletnie skomplikował sprawę. Biblioteki, które miały imitować jego działanie zawsze miały jakieś ograniczenia. Najgorsza jest świadomość, że kod JS przez przeglądarkę pewnie też jest kompilowany do jakiegoś swojego kodu, który na pewno zawiera instrukcje skoku.

Kolejne rozwiązanie to bezpośrednia interpretacja kodu. Tworzyłem po prostu funkcję, która w domknięciu dostawała tablicę z kodem, pulę stałych i co tam jeszcze było potrzebne. W momencie wykonanywania przygotowywała swój stos itd., a następnie interpretowała każdy bajt po kolei.

Trzecim rozwiązaniem, którego używam w tej chwili jest zmielenie kodu do własnej struktury, która jest tablicą funkcji. Każda instrukcja jest przerabiana na pojedyńczą funkcję. Fragment który wcześniej interpetował kod teraz po prostu wykonuje po kolei funkcje z tej tablicy.

Idealnie byłoby generować kod JS podobny do pierwotnego kodu w Javie, ale to odpada po pierwsze dlatego, że pisanie dekompilatora jest raczej trudne, a po drugie nawet te dekompilatory, które są nie zawsze potrafią wygenerować pierwotny kod. Oczywiście postaram się jakoś zbliżyć do tego, ale na razie obecna wydajność wydaje się wystarczająca.

Natywne klasy

Wszystkie klasy dostarczane razem z implementacją są napisane w JS. Oczywiście obudowałem je trochę, żeby imitowały te wygenerowane z pliku .class. Dzięki temu są dla siebie nawzajem widoczne z klasami Javy.

Typy prymitywne

Ponieważ w JS wszystkie liczby są tym samym typem zrobiłem takie oto mapowanie:

  • boolean -> number (JVM trakuje boole jak liczby)
  • integer -> number
  • short -> number
  • byte -> number
  • char -> number (na początku był stringiem, ale z tym były czasem problemy)
  • long -> js2me.Long (number ma zbyt małą precyzję, żeby trzymać w nim liczby 64-bitowe, dlatego trzymam w dwóch, musiałem przez to zaimplementować własną arytmetykę, koszmar)
  • float -> number
  • double -> js2me.Double (tak, akurat ten typ, który jest odpowiednikiem number musiałem ubrać w obiekt, bo long i double są czasem traktowane inaczej niż pozostałe typy i potrzebowałem rozróżnienia)

Oprócz tego odpowiednie instrukcje odpowiedzialne za arytmetykę integer, short i byte symulują w razie czego overflow. Co do floata to wydaje mi się, że nie będzie większej tragedii, jeśli będzie zbyt precyzyjny.

Wątki

To chyba najbardziej porąbana rzecz w tym projekcie. JS nie ma niczego, co by mogło nadawać się na wątki. Oczywiście każdy porządny programista JS natychmiast pomyśli o WebWorkerach, niestety podczas komunikacji z głównym wątkiem są przesyłane jedynie kopie obiektów. Zbyt dużo problemów, żeby się w to pakować, a Keon i tak ma jeden rdzeń. Rozwiązanie: symuluję wątki samemu. Np. kiedy jakaś metoda wywoła metodę sleep natywna implementacja ustawia globalną flagę js2me.suspendThread, metoda wywołująca dostaje sygnał, żeby kończyć imprezę. Wszystkie wygenerowane metody, gdy zobaczą aktywowaną powyższą flagę wrzucają informacje o bierzącym stanie wykonywania na stosik przyporządkowanym pod aktualny wątek. Sytuacja eskaluje coraz niżej, aż się nie skończy się drzewo wywołań. Wtedy mój stos ma wszystkie informacje, żeby potem móc dokładnie odtworzyć moment usypiania. Oczywiście sleep zostawia po sobie timeouta, który w odpowiednim czasie odtwarza stos. Głupie, ale skuteczne

Ta technika przydaje się również w przypadku ładowania obrazków (BTW są one normalnymi obrazkami HTML, które jako src mają ustawiany DataURI stworzony na podstawie danych z pliku), gdzie przeglądarka robi to asynchronicznie, a w J2ME dzieje się to synchronicznie. Wtedy usypiam wątek dopóki nie załaduje się obrazek.

Rozwój projektu

Co prawda roadmapa jest publicznie dostępna, ale zamierzam tutaj dokładniej opisać jak powstawał ten projekt. Jak widać w pliku, który pewnie teraz otworzyłeś na pierwszy ogień poszedł Hello World. Wzięty na szybko z jakiegoś tutoriala i skompilowany. Jak sama nazwa mówi ta aplikacja nie robi nic innego oprócz wyświetlania napsiu "Hello World". Nikt kto nie tworzył nigdy podobnego projektu nie wie jaka to radość zobaczyć taką pierdołę. Dojście do poziomu uruchomienia jej zajęło mi kilka dni.

Potem poszedłem trochę ambitniej i wziąłem jakąś gierkę napisaną J2ME lata temu. Powód był prosty, znałem ją, wiedziałem jak działa, miałem kod źródłowy, a złożoność nie była zbyt duża. Gdy tylko poczułem się na siłach poszukałem cudzych i większych projektów, ale koniecznie open source, żeby móc się wesprzeć kodem źródłowym. Klon Asteroids (licencja GPL), który znalazłem nawet dołączyłem do repozytorium jako swego rodzaju prezentację dla osoby, która by chciała uruchomić mój projekt. Potem próbowałem kolejne aplikacje, niektóre udało mi się doprowadzić do działania, przy innych się poddawałem. Potem postanowiłem zająć się wydajnością i wynalazłem FPC benchmark. Oczywiście musiałem się trochę pomęczyć, żeby go odpalić, ale w końcu ruszył i wskazał mi wynik na poziomie bardzo starych telefonów (FPC ma w necie bazę wyników), zdecydowanie starszych niż przeciętne telefony w czasach, gdy J2ME miał swoje lata świetności. Tym bardziej, że uruchamiałem go na komputerze stacjonarnym. Procesor miałem skręcony do 800 MHz. Keon ma rdzeń A5 (jedna z najsłabszych architektur) taktowany na 1GHz (czyli musiałbym zejść pewnie do jakichś 400 MHz, żeby się z nim zrównać wydajnością). Na mojej komórce (Scorpion 1GHz, wciąż trochę lepszy od A5) ten test trwał tak długo, że wolałem wyłączyć. Wtedy dopisałem czwartego Milestone'a i kompletnie zmieniłem sposób wykonywania kodu bajtowego (dokładny opis kilka podpunktów wyżej). W połączeniu z kilkoma innymi optymalizacjami osiągnąłem wynik kilkanaście razy lepszy i na razie odpuszczam sobie dalszą optymalizację, chociaż spodziewam się, że może być kiedyś potrzebna.

Obecny stan

Na razie projekt jest na takim poziomie, że całkiem przyzwoita ilość aplikacji działa. Nie mówię tylko o tych, które używałem przy rozwoju, miałem sporo sytuacji, kiedy ściągnąłem jakąś aplikację dla testów, a ona działała bez błędów albo z drobnymi błędami. Na ten moment z prawie 200 instrukcji mam zaimplementowane niemal wszystkie (wedle mojej rozpiski brakuje dwunastu). Na 145 klas i interfejsów prawie wszystkie są zaimplementowane znikomo lub częściowo (w pełni chyba tylko 10, poza tym odpada wiele interfejsów, bo tam zwykle nie ma co implementować). W każdym razie obecny stan oceniam na tyle pozytywnie, że można z tego zacząć kleić aplikację, bo na razie to głównie silnik z paroma końcówkami, żeby móc go testować. Poza tym kolega załapał się na darmowy telefon z Firefox OS i dobrze było by sprawdzić aplikację na żywym sprzęcie.

Przyszłość

  • Midlet suite - Czyli sytuacja, kiedy aplikacja ma kilka Midletów. Specyfikacja dopuszcza taką sytuację, ale w praktyce to jest prawie niespotykane. Na razie odpalam tylko pierwszy, lepszy z brzegu Midlet.
  • MIDI - Może to brzmi niewiarygodnie, ale Gecko nie obsługuje natwynie tego formatu. W sieci znalazłem MIDI.js (uwaga: migoczące kolory i wkurzająca muzyczka), ale trochę boję się o wydajność tego rozwiązania. Na razie w grach wykorzystujących ten format (czyli 99% gier J2ME) można posłuchać uspokajającej ciszy.
  • Blutetooth, SMS, TCP - Czyli wszystko co wymaga spięcia z API Firefox OS. Na razie wygodniej jest testować w przeglądarce i używanie symulatora to ostateczność
  • M3G - Te tajemnicze literki to jedno z bardziej wymagających rozszerzeń, potrzebne do uruchomienia gier w 3D.
  • ...i inne - Oprócz podanych wyżej jest sporo innych rozszerzeń, na szczęście rzadko wykorzystywane, ale wypadałoby kiedyś je dodać.

tl;dr; Zrobiłem emulator J2ME, tu jest kod: https://github.com/szatkus/js2me

Python vs Scala

Postanowiłem dzisiaj porównać ulubiony (wahałem się użyć tego słowa, bo to jednak tylko narzędzie) język programowania czyli Pythona z ciekawie się zapowiadającym, jeszcze nieodkrytym przez mnie do końca językiem Scala. Postaram się opisać je w miarę bezstronnie. To nie będzie trudne, bo jestem równie entuzjastycznie nastawiony do obydwu :) Też w żaden sposób nie widzę w nich konkurencji. Na razie parę faktów z wikipedii o obu językach.

Python

Powstał w 1991 roku, jest językiem obiektowym i proceduralnym z silnym, dynamicznym typowaniem i elementami funkcyjnymi. Ostatnia wielka zmiana nastąpiła w roku 2008, kiedy wydano Pythona 3. Główną implementacją jest CPython, który jest interpreterem w języku C, ale z biegiem lat powstało też wiele innych. Zasady jakie mu przyświecają to czytelność, prostota i zwięzłość. Głównie używany wśród naukowców i ekosystemie Linuksa jako prostsza alternatywa dla C/C++ w aplikacjach desktopowych. Bardzo trafnie kiedyś określony jako "kompilowalny pseudokod".

Scala

Język stworzony przez ludzi, którym przeszkadzała różne braki w Javie, pierwsza wersja powstała w 2003. Ostatnia większa zmiana w 2006 roku (wersja 2.0). Całkowicie obiektowy z silnym, statycznym typowaniem i również z elementami funkcyjnymi, ponadto zawiera mechanizmy wspierające obliczenia współbieżne. Kod źródłowy można skompilować pod maszynę JVM lub .NET. Stawia na prostotę, skalowalność i funkcyjność. Używany głównie przez twórców i znudzonych javowców :) Nazywany "lepszą Javą".

Typowanie

Fundamentalna sprawa. Wiele w internecie można znaleźć dyskusji na temat dynamicznego vs statycznego typowania. Można z nich (podobnie jak z prawie wszystkich dyskusji typu jabłka vs gruszki) wysnuć jeden wniosek: to zależy. Dynamiczne typowanie upraszcza wiele rzeczy, daję większą swobodę, dopuszcza monkey-patching (modyfikowanie instancji obiektów w trakcie działania programu). Statyczne typowanie zwiększa czytelność i daje większe bezpieczeństwo. Dlatego nie zamierzam porównywać co jest lepsze, bo to trzeba dopasować do sytuacji. Dlatego też osobiście mam zamiar używać obu języków.

Benchmarki

Jeśli chodzi o wydajność oparłem się o wyniki The Computer Language Benchmarks Game. Wynika z nich, że Scala jest sporo szybsza. Python za to zajmuje mniej kodu i ma mniejsze zapotrzebowanie na pamięć. Nie jest to niespodzianką, wiadomo, że JVM generuje szybki kod i zżera bardzo dużo pamięci. Standardowy Python to tylko interpreter, więc nie można było liczyć na wiele. Niestety nie ma wyników dla PyPy. Postaram się w wolnej chwili przygotować własne. Zrobiłem tylko kilka na szybko, w których Scala miała co najwyżej trzykrotną przewagę nad PyPy i 50-krotną nad standardowym Pythonem.

Moduły

W Pythonie moduły z poziomu języka widoczne są jako obiekty. Takie obiekty mogą zawierać w sobie funkcje, klasy, a także luźny kod, który jest wykonywany w momencie importu. Co ważne import można wykonać w niemal dowolnym bloku kodu. W Scali wygląda to bardzo podobnie, ale oczywiście jest całkowicie obiektowa i nie można w module umieścić luźnych funkcji i kodu. Poza tym modułem jest katalog (wszystkie klasy, interfejsy itp. w plikach źródłowych po skompilowaniu "wyskakują" jako osobne pliki tak samo jak w Javie), a nie plik z modułem jak w Pythonie.

Obiektowość

W jednym i drugim języku wszystko jest obiektem, jednak Scala wykazuje się tu większą spójnością. W Pythonie metody wywołuję się przez konstrukcję obiekt.metoda(), a operator np. dodawania a + b wywołuje a.__add__(b). W Scali są dwie możliwości wywoływania metod: a.metoda() (() opcjonalne) i a metoda. Ta druga konwencja moim zdaniem zmniejsza czytelność w przypadku zwykłego kodu, ale bardzo elegancko rozwiązuje sprawę operatorów. Mianowicie a + b to tak naprawdę a.+(b). Pozwala to tworzyć własne operatory, podczas gdy Python ogranicza nas tylko do kilku wbudowanych. Warto wspomnieć, że oba języki mają akcesory i mutatory (gettery i settery, ale trzeba dbać o czystość języka polskiego :P) deklaruje się dużo bardziej elegancko niż w Javie. Jeśli stwierdzimy, że jakieś pole oprócz przy przypisywaniu lub odczycie ma robić coś więcej to w Pythonie używamy dekoratorów (adnotacje dla javowców, "małpki" dla pozostałych), a w Scali definiujemy odpowiednie metody (zadanie domowe: wymyśl jak to zrobić, cała wiedza potrzebna do tego jest w tym akapicie... (Scala pozwala zastępować znak "_" w nazwie metody na spację)... teraz już jest :)).

Scala też ciekawie radzi sobie z metodami statycznymi. Klasy nie mogą zawierać statycznych metod i pól. Po to powstał taki twór jak obiekty (object). Istnieje tylko jedna instancja obiektu, nazwy mogą się pokrywać z nazwami klas, przez co jeśli chcemy stworzyć klasę z "normalnymi" metodami i statycznymi musimy rozbić to na klasę i obiekt o tych samych nazwach. Szczerze mówiąc nie widzę zbyt wielu zalet takiego podejścia. W Pythonie gdy chcemy stworzyć jakiegoś helpera wyładowanego statycznymi metodami to po prostu robimy moduł z funkcjami. Z zewnątrz wygląda to identycznie jak w Scali, czyli jedna instancja obiektu z metodami. Jeśli chcemy w Pythonie klasę mieszaną to metody statyczne oznaczamy odpowiednimi dekoratorami (podobnie jak w innych językach, tylko zazwyczaj to są słowa kluczowe).

Jeśli chodzi o składnię to Scala z jednej strony daje możliwość pisania podobnie jak w Pythonie (bloki oparte o wcięcia, jednolinijkowe "czyste" funkcje, bez średników), a z drugiej pozwala pisać podobnie jak w Javie (bloki oparte o klamry i średniki).

Polimorfizm w Pythonie to po prostu zwykłe wielokrotne dziedziczenie, natomiast w Scali są znane z Javy pojedyńcze dziedziczenie i interfejsy, a ponadto cechy (trait, ale tak to można chyba tłumaczyć) czyli struktury z zaimplementowanymi metodami, które można doczepiać potem do klas.

Hermetyzacja w Scali jest właściwie identyczna jak w Javie, natomiast w Pythonie można utrudnić dostęp do niektórych atrybutów obiektu poprzez dodanie "_" lub "__" w nazwie, ale jeśli się chce to do wszystkiego można się dostać.

Zmienne

W Pythonie nie deklarujemy zmiennych, po prostu przypisujemy jakąś wartość, a interpreter w razie potrzeby utworzy zmienną o podanej nazwie. W przypadku odwoływania się do zmiennych globalnych trzeba użyć słowa kluczowego global, ale używanie zmiennych globalnych to zazwyczaj nie jest dobry pomysł :) W Scali zmienne deklarujemy przez var nazwa[:typ], a stałe (oczywiście tylko referencja jest stała, obiekt już niekoniecznie) przez val nazwa[:typ]. Jak widać typ jest opcjonalny, o ile kompilator jest w stanie sam się domyśleć jaki typ jest nam potrzebny.

Biblioteka standardowa

Tu też Scala jest bardziej spójna niż Python. Mimo że wersja 3.0 wprowadziła trochę porządku to i tak są pewne rozbieżności jeśli chodzi o nazewnictwo i styl. Scala jako, że ma działać zarówno na JVM jak i na .NET, wprowadza swoje nakładki na klasy w poszczególnych środowiskach. Dzięki temu jeden kod może działać na obu maszynach (oczywiście tylko ten bez zbędnych luksusów jak baza danych albo GUI :)). Co najważniejsze Scalowe wersje standardowych obiektów zawierają wiele nowych metod (głównie z dziedziny programowania funkcyjnego) w porównaniu z natywnymi odpowiednikami. W Pythonie od zawsze denerwowała mnie taka na wpół obiektowa składnia w najbardziej podstawowych obiektach. Z jednej strony możemy wywołać "a b c".split(), ale żeby sprawdzić długość trzeba wywoływać funkcję len("cośtam"), która wywołuje metodę "cośtam".__len__(). Nie wiem czemu w wersji 3.0 się tego nie pozbyli. Scala za to wprowadziła pewną rzecz, która wcześniej bardzo mi się podobała w Haskellu. Na wstępie ładuje obiekt scala.Predef i jeśli wywołamy "funkcję" np. println to tak naprawdę wywołujemy odpowiednią metodą z obiektu Predef.

Programowanie funkcyjne

W jednym i drugim wygląda to bardzo dobrze. Różnica jest taka, że Python zgodnie ze swoją filozofią trzyma funkcje związane z programowaniem funkcyjnym (tak, chciałem napisać funkcje funkcyjne :)) w module functools, a w Scali są to oczywiście metody w odpowiednich obiektach (np. kolekcjach). Oczywiście oba języki mają funkcje lambda wbudowane w składnię, a Python nawet całkiem eleganckie definiowanie zbiorów.

Refleksja

W tym punkcie nie ma niespodzianki, Python po prostu miażdży Scalę. Możemy zmieniać instancje i deklaracje klas w locie, a nawet tworzyć metaklasy i przeładowywać moduły. Do tego każdy obiekt zawiera metody specjalne, który możemy nadpisywać dla osiągnięcia ciekawych efektów, np. tworzyć w locie metody na podstawie nazwy (__getattribute__). Scala pozwala nam właściwie tylko na tyle, na ile pozwala JVM. Jest o tyle lepiej, że funkcje są obiektami, ale tylko dlatego, że kompilator je sprytnie opakowuje w klasy. Oczywiście dynamicznie załadować albo stworzyć klasę możemy, ale nadpisać już załadowaną tylko w trybie debugowania.

Dokumentacja

Jeśli miałbym pokazać największe wady Pythona, to na pewno na pierwszy miejscu wyląduje dokumentacja. Jest straszna. Tak jak bardzo lubię ten język, tak używanie dokumentacji jest zazwyczaj koszmarem. Brak jakiegoś sensownego uporządkowania, zawsze mam problemy z poruszaniem w wygenerowanych PyDocach. Żeby nie było to czepiam się tylko wersji HTML, w trybie interaktywnym dokumentacja jest bardzo pomocna (wywołuje się ją przy pomocy funkcji help(obiekt) lub help(obiekt.metoda)).

ScalaDoc (bo tak oczywiście się ten twór nazywa) to po prostu ulepszony JavaDoc. A ponieważ JavaDoc jest świetny, więc ScalaDoc jest nawet trochę lepszy. Najbardziej podoba mi się, że z każdej strony można przejść do odpowiadającego kodu źródłowego. Bardzo przydatne jest też filtrowanie i wyszukiwanie. JavaDoc powstał w czasach, gdy JavaScript nie był tak mocny jak teraz więc nikomu nie przyszło wtedy dodanie takich funkcji.

Paczkowanie

W tym aspekcie między obydwoma językami jest duża różnica... filozoficzna. Python jak wcześniej pisałem jest mocno związany ze środowiskiem Linuksa, tak że moduły powinny leżeć w systemie i być instalowane z repozytorium dystrybucji, ewentualnie z repozytorium Pythona. Oczywiście jeśli jest taka potrzeba można instalować moduły per użytkownik albo nawet tworzyć izolowane środowiska z virtualenv. Jeśli chodzi np. o użytkowników Windowsa to można wszystkie moduły i interpreter spakować do jednego execa i wtedy użytkownik nie musi się przejmować zależnościami.

Scala wywodzi się z Javy, która wszystkie zależności trzyma w projekcie. Tutaj jest Maven i inne tego typu narzędzia. To już kwestia gustu, które podejście nam bardziej odpowiada.

Współbieżność

Nie czuję się kompetentny w temacie, nie tworzyłem nigdy projektu, który by miał więcej niż kilka wątków :) Scala była projektowana pod programowanie współbieżne, więc ma takie bajerki jak aktorów i kolekcje współbieżne, pewnie na tym polu wygrywa.

Podsumowanie

Jak pisałem na wstępie, nie zamierzam oceniać, który jest lepszy. Wedle mnie obydwa języki są świetne w różnych tematach. Bardzo bym też chciał, żeby twórcy Pythona podpatrzyli parę rzeczy od Scali i umieścili kiedyś w Pythonie 4.0 :) W Scali też nie jest wszystko idealne, ale widać, przez to, że język był projektowany na początku wieku i twórcy mogli czerpać z wieloletnich doświadczeń twórców innych języków, jest znacznie bardziej przemyślany niż większość tych starszych. Dużą zaletą jest też to, że można projekty napisane w Scali niemal bezboleśnie podczepić pod istniejące projekty Javy i odwrotnie. Za to Python daje ogromną łatwość pisania kodu i zwięzłość. Mam nadzieje, że z pomocą moich, umieszczonych tutaj obserwacji każdy sam sobie wyrobi opinię o tych językach.

A, i jeszcze jedno. Oba języki są bardzo rozbudowane i temat nie był łatwy do opracowania. Mimo, że starałem się wybadać wszystkie rzeczy, które tutaj pisałem, mogłem o czymś nie wiedzieć/zapomnieć/nie zauważyć. Wszelkie znalezione błędy oczywiście proszę wytykać w komentarzach :)