środa, 28 listopada 2007

Odpowiednie narzędzia do pracy to połowa sukcesu

Esencją tego bloga jest informowanie o narzędziach programistycznych, jakich można użyć do rozwiązania problemów codziennej pracy. Do tej pory jednak skupiałem się na językach programowania, a przecież kod, który piszemy musi w jakiś sposób powstać. I nie zamierzam tu zaczynać kolejnej wojny o wyższości Vim nad Emacs, Eclipse nad Netbeans, Scite nad Textpad itd. Chciałbym się podzielić swoim (może ciągle niewielkim) doświadczeniem z programowania w Ruby oraz Haskellu.

Bez wielkiego wstępu i rozwodzenia się, uważam że do tworzenia oprogramowania w Ruby najlepszym środowiskiem jest Netbeans 6. Dzięki pracy autorów "Ruby plugin" możemy cieszyć się komfortem pisania z podpowiadaniem składni, dokumentacją API, debuggerem, refaktoryzacją oraz pełną integracją z samym IDE. Na obecną chwilę, Netbeans (w wersji 6.0) nie ma godnej konkurencji.

Opisując Netbeans nieprzypadkowo użyłem określenia "najlepsze środowisko", ponieważ do szybkiego edytowania kodu w Ruby (oraz Haskell) używam jEdit.

Jest to bardzo mocno rozszerzalny i konfigurowalny edytor z podświetlaniem składni, zaawansowanym formatowaniem tekstu i obsługą wielu plików na raz. Jednak dopiero po uzupełnieniu podstawowej funkcjonalność o wtyczki (np. wspaniałą SuperAbbrevs) zamienia się w prawdziwy kombajn. Polecam - wypróbujcie jEdit! A - czy wspomniałem już że jest całkowicie darmowy? :-)

wtorek, 20 listopada 2007

Różne podejścia do wielowątkowości

Pisałem ostatnio o języku Haskell. Uważam go za jeden z lepszych w dziedzinie programowania wielowątkowego w modelu "tradycyjnym", czyli z współdzieloną pamięcią (dzięki połączeniu czysto języka funkcyjnego i technologii STM - bardzo przystępnie przedstawionej w "Beautiful concurrency"). Nie jest jednak jedyne możliwe podejście do problemu. Oto krótkie zestawienie stosowanych rozwiązań wraz z komentarzem:
  • brak jakiejkolwiek obsługi wielowątkowości (Javascript, PHP) - wbrew pozorom to też jest jakieś rozwiązanie, w końcu nie zawsze potrzeba takiej funkcjonalności
  • pseudo-wątki obsługiwane przez runtime języka (Python, Ruby) - w językach z pseudo-wątkami (określanymi często w j. angielskim jako "green threads") to środowisko uruchomieniowe języka samo zajmuje się obsługą współbieżności w najprostszej formie, czyli przełączania kontekstu pomiędzy wątkami. Stosując takie rozwiązanie nie da się w pełni wykorzystać potencjału współczesnych procesorów wielordzeniowych czy maszyn wieloprocesorowych
  • wątki natywne operujące na współdzielonej pamięci (C, Java, Haskell) - języki te udostępniają interfejs umożliwiający operacje na wątkach sterowanych przez system operacyjny (ang. "native threads"). Rozwiązanie to cechuje z jednej strony możliwość tworzenia bardzo wydajnych aplikacji, ale z drugiej duży stopień trudności związany z zapewnieniem poprawności działania. To tu pojawiają się zagadnienia synchronizacji dostępu, blokad, semaforów itp.
  • niezależne, natywne lub kontrolowane przez runtime wątki z systemem komunikatów (Erlang) - według wielu znawców tematu tylko to rozwiązanie ma przyszłość w świecie, w którym -dziesiąt czy -set rdzeni lub procesorów przestaje być fantazją. Atutem jest tu niemal nieograniczona skalowalność takiego podejścia, wadą - konieczność zaadoptowania diametralnie innej koncepcji budowy oprogramowania.
Jeszcze słowo komentarza o współdzielonej pamięci. Moim skromnym zdaniem model ten nie jest skazany na wymarcie (a jeśli nawet, to nie w najbliższej przyszłości). Zbyt wiele jest aplikacji, które o ile skorzystają na wprowadzeniu współbieżnego działania, to nie są narażone na wielowątkowość w skali większej niż kilka równoległych wątków. Tak czy inaczej, zawsze znajdzie się odpowiednie narzędzie do rozwiązania problemu.

piątek, 9 listopada 2007

Procesory wielordzeniowe a programowanie

Miesiąc temu obiecałem napisać o rewolucji w sposobie myślenia o pisaniu programów. Zdradziłem wtedy, że chodzi o język Haskell. Czym więc jest Haskell? Jest językiem programowania (to raczej nie jest niespodzianka) o następujących cechach:
  1. czysto funkcyjny - o ile programowanie funkcyjne jest samo w sobie diametralnie innym podejściem do tworzenia oprogramowania, to dopiero "czysta" funkcyjność Haskella jest prawdziwym unikatem, o którym za chwilę
  2. funkcje jako wartości podstawowe - funkcje mogą być parametrami lub wartościami wynikowymi innych funkcji
  3. currying funkcji - w największym skrócie chodzi o to, że podając np. dwuparametrowej funkcji "dodaj" jeden parametr (np. 3), otrzymujemy w rezultacie nową funkcję jednoparametrową (którą można by nazwać "dodaj3do")
  4. z silną i statyczną kontrolą typów - nie zamierzam bynajmniej rozpoczynać tu dyskusji o wyższości świąt Bożego narodzenia na świętami Wielkiejnocy (w wydaniach "silna czy słaba" / "statyczna czy dynamiczna" kontrola typów), zawsze sugeruję użycie najbardziej odpowiedniego narzędzia w określonej sytuacji
  5. z odgadywaniem typów - interpreter lub kompilator potrafi (w zdecydowanej większości przypadków) poprawnie "odgadnąć" typ wyrażenia, więc programista nie musi go jawnie deklarować; tak przy okazji, to połączenie statycznej kontroli typów z ich odgadywaniem typów jest bardzo dobrym rozwiązaniem, bo pozwala ograniczyć charakterystyczną dla statycznie typowanych języków "rozwlekłość" kodu
Wyjaśnienia wymaga przede wszystkim "czystość" podejścia funkcyjnego zastosowana w Haskellu. Autorzy języka trzymają się ściśle definicji funkcji wyniesionej z dziedziny, która dała nam funkcje - matematyki. Jednym z założeń funkcji jest to, że nie może wpływać ona na otoczenie oraz to, że dla danego parametru wejściowego zawsze daje ten sam wynik. Takiego założenia nie spełnia np. najprostsza "funkcja" (można by ją dla uniknięcia niejednoznaczności określić jako "procedura") pobierania znaku z klawiatury - getChar. Wiele innych języków funkcyjnych przymyka na ten fakt oko i nie robi z tego powodu żadnego problemu. Haskell idzie krok dalej i, korzystając z bardzo abstrakcyjnego działu matematyki zwanego teorią kategorii, wprowadza wyraźny podział na właściwe funkcje (spełniające wymagania definicji funkcji) oraz monady (nie wiem niestety czy to poprawne polskie określenie, równie złe ale dające pewien punkt odniesienia to "akcje"). O tym czym są monady w kontekście języka Haskell pewnie jeszcze napiszę przy okazji (to ulubiony temat blogów o Haskellu), na razie powinna wystarczyć informacja, że Haskell rozróżnia funkcje i akcje.

Całe to zamieszanie z czystością podejścia funkcyjnego raczej odstrasza niż zachęca. Dlaczego więc twórcy Haskella tak się przy tym upierają? Jakie korzyści może to przynieść? I co to wszystko ma wspólnego z tematem posta - procesorami wielordzeniowymi?

Własności funkcji matematycznej - funkcja zawsze da ten sam wynik dla określonego parametru i nigdy nie wpłynie na wywołanie innej funkcji - sprawiają, że jej zastosowanie w programie komputerowym daje potężne możliwości optymalizacji dla kompilatora lub interpretera. Nie ma też żadnego ryzyka związanego z uruchomieniem np. 32 współbieżnych wątków obliczających dowolną funkcję (czego nie można powiedzieć o uruchomieniu chociażby 2 współbieżnych wątków dokonujących modyfikacji w systemie plików). A nie ma lepszej metody na wykorzystanie coraz bardziej popularnych procesorów wielordzeniowych niż aplikacje wielowątkowe.

Chwila - wielu czytelników zapewne teraz zwróci uwagę - aplikacje wielowątkowe to przecież żadna nowość. Zaletą podejścia w Haskellu jest jednak to, że jawnie oddzielone są od siebie bezpieczne bloki kodu (gdzie bezpieczeństwo oznacza "możliwość wpływania na swoje otoczenie") od potencjalnie niebezpiecznych.