Najpiękniejszy kawałek kodu jaki widziałem

13 Oct 2008

Może wydać się to śmieszne, że ludzie potrafią doświadczać zjawiska piękna w tak rutynowej czynności jaką jest programowanie. Ale robią to. Kiedyś, jeszcze na studiach, wielki szacunek wzbudził u mnie znajomy, który z ogromną pasją opowiadał o algebrze (pierścienie, ciała, alefy, przekształcenia izomorficzne) i podkreślał jej wewnętrzną harmonię, spójność, siłę oraz eww.. piękno. Dzisiaj poczułem się dokładnie w ten sam sposób oglądając przykład z mojej dziedziny.
Oto on (Javascript):

function main(links) {
  for (var i=0; i<links.length; i++) {
    links[i].onclick = (function(j) {
      return function() {
        alert(j+1);
      }
    })(i);
  }    
};

main(document.getElementsByTagName("a"));

Na pierwszy rzut oka jest on mało czytelny - ilość nawiasów właściwa dla programów napisanych w Lispie, więcej niż 4 wcięcia (argument K&R w przypadku języka C: jeśli potrzebujesz więcej niż 4 wcięć to znaczy, że z twoim programem jest coś nie tego), w dodatku w liniach 3 do 7 dzieje się coś magicznego. I to właśnie ta magia, gdy zrozumiana, jest esencją piękna.

Nie będę trwonił słów próbując ze słabym najpewniej skutkiem wyjaśnić co tak naprawdę wydarza się w tym zagnieżdżeniu. Dużo lepiej zrobił to Stuart Langridge w swoim wystąpieniu (w dodatku gość ma zabójczy akcent!). Gorąco polecam materiał video - to prawdziwy eye-opener, który pozwala dostrzec potęgę Javascriptu w ogólności, a Javascriptowych domknięć (closures) w szczególności. W dodatku przybliża on ideę programowania obiektowego opartego o prototypy (nie o klasy) oraz składowe prywatne i zaprzyjaźnione w obiektach Javascriptowych. Znający Pythona być może będą się ze mnie śmiać gdy wyznam, że nie potrafiłem sobie wyobrazić traktowania funkcji jako obiektu, a dokładniej konstruktora tego obiektu (niech żyje this!). Jakość nagrania nie zawsze pozwala czytać z obrazu rzutnika, więc warto podeprzeć się prezentacją w OpenDocument.

Pomimo, że przykład powyżej to jedynie marna funkcja (na codzień gardzę funkcjami nienależącymi do klas) z trywialną iteracją (na codzień gardzę programowaniem imperatywnym), to jednak anonimowe domknięcie wykonywane natychmiast i zwracające kolejne domknięcie jako delegata zniewala (mnie osobiście) swoją elegancją i siłą.

Digg del.icio.us StumbleUpon Wykop Reddit Folksr

permalink | trackback | rss

 
 
jajcus

Mnie się teraz dziwne wydają języki w których nie można funkcji, czy klasy potraktować jak każdego innego obiektu i np. przekazać jako argument... Musząc coś poprawiać w Javie czułem się strasznie przez to ograniczony.

stronger

To właśnie dzięki Tobie wiem o takich "niezwykłych" (dla programisty PHP głównie) właściwościach Pythona i jego zdolności dobierania się do wszystkiego jak do obiektu. W ogóle języki z enterprajsowego mainsteamu, jak Java czy C# są po prostu nudne, natomiast prawdziwe perełki leżą na obrzeżach - Javascript, Python, Erlang. W takiej kolejności mam ochotę się nimi zainteresować.

e

delegatki w dżawce:
http://chaoticjava.com/posts/delegates-in-java-why-not/
http://www.egjug.org/delegates
http://www.onjava.com/pub/a/onjava/2003/05/21/delegates.html

i na deser Microsoft vs Sun:
http://java.sun.com/docs/white/delegates.html

stronger

e, wiemy że się da. Ale po prostu delegaty nie są wpisane w naturę tego języka. Innymi słowy, pisząc w Javie pewnie użyłbym wzorca Command. W Javascripcie delegaty/domknięcia są dość naturalne, a składnia zachęca do ich stosowania.

teamon

Mi sie bardziej podoba: http://pastie.org/private/ve3erjtihwussq5knl0mq ;]

jajcus

teamon: za dużo znaków interpunkcyjnych ;-P

stronger

teamon, o jezu - głowa boli i nogi ;-) Domyślam się, że to Ruby (btw, składnia tablica do |x| jest również godna pozazdroszczenia)

Teamon

Ruby, ruby ;] Masz na myśli |*args| ?

stronger

Que oui!

Teamon

To jest po prostu zamiana wszystkich parametrów bloku (w tym wypadku to w sumie metody która jest tworzona poprzez blok) na tablice ;)

Shot

Miałem pisać, żebyś Ruby’ego dodał do listy – ale widzę, że koledzy ubiegli. :)

stronger

Z Rubym mam ten problem, że jak dla mnie robi on mniej więcej to, co Python, tylko ma węższe pole zastosowań i/lub mniej okrzepłą praktykę (np. bindingi GTK+), derfor wolę go poświęcić na rzecz czegoś na prawdę innego, np. Erlanga. Poza tym, mam kilka zastrzeżeń do Rubiego, które mogą posłużyć za niezły flamebait.

Teamon

Co kto lubi. Ja przeszedlem do Ruby z PHP. Próbowałem ostatnio troche pythona, ale jakos mi nie lezy. A podyskutować zawsze mozna ;)

Shot

Czemu węższe pole zastosowań? (Bindingów nie obczajałem, to siedzę cicho.)

Jeśli faktycznie musisz wybrać, to jasna sprawa; też bym się nie zastanawiał, jeśli więcej rzeczy Ci za Pythonem przemawia. Ale flame’a poprosimy, tak. :)

(A co rzeczy innych – czemu Erlang/Haskell, a nie Lisp?)

stronger

Wiesz Shot, to nie kwestia musu tylko zwykłego komonsensu - w praktyce będę szczęśliwy gdy starczy mi czasu na zgłębienie nawet jednego języka (zapomniałem, że priorytet na razie ma hiszpański ;-)

Za Pythonem przemawia choćby to, że pisanie aplikacji tak webowych jak i okienkowych jest powszechne, sprawdza się też jako język dla komponentów UNO w OpenOffice. No i zawsze będę mógł zawrócić gitarę Jajcusiowi po starej znajomości ;-)

Erlang z racji tego, że bardzo interesuje mnie CouchDB oraz zagadnienia związane z Map-Reduce i jego ograniczeniami. W ogóle to polecam podcast o tym języku na Software Engineering radio - http://se-radio.net/podcast/2008-03/episode-89-joe-armstrong-erlang

A Lisp? Nie chce mi się uczyć języka tylko po to, by sobie podkonfigurować edytor ;-) Chyba że znasz jakieś zastosowania Lispa, które będzie dobrze wyglądać w CV?

stronger

Niech stracę. Kulturalne tu ludzie, nawet interpunkcji używajo, może nie będzie flejma.

Po pierwsze primo, pierwszy argument podnoszony głównie przez Railsowców mówiących, że każdą część frameworka czy biblioteki można nadpisać, przepisać, zreimplementować. Odrzuca to mnie z powodów praktycznych. Co jeśli zastępcza implementacja psuje inną część aplikacji, która współpracowała z jej poprzednią wersją? Nadpisanie metody może również powodować niespójność z dokumentacją (tak - dokumentuję swój kod). Jaki jest koszt błędów popełnianych przez nowych programistów w projekcie, oczekujących starej implementacji?

Po drugie primo, Mixins, czyli w nomenklaturze PHP Traits. Cechy to narzędzie, które oducza myślenia i gwarantuje, że przeciętni programiści będą tworzyć trudny do śledzenia kod z licznymi zależnościami. Dzięki cechom można szybko i sprawnie powiązać ze sobą klasy i funkcjonalności w sposób, który jest obrazą dla zdrowego rozsądku. Jest to klasyczny przypadek good intentions gone bad.

Po trzecie primo, co nie jest już winą samego języka, to relatywnie niska podaż ofert pracy. Lub raczej, ofert pracy w pożądanej przeze mnie lokalizacji.

Teamon

I tak i nie. Twórcy Railsow (i pluginów do nich) czesto kożystają z tzw. monkey patching. Merb odchodzi od takich praktyk - http://oldwiki.merbivore.com/pages/it-is-a-bug-if. Jednak dopisywanie metod do klas czasami moze byc jest bardzo przydatne (np do String, warto spojzec na extlib merba/dm)

Co do mixinów, imo lepiej mieć niż nie mieć. Juz choćby podstawowe moduły jak Comparable czy Enumerable ułatwiją operacje na własnych obiektach.

Shot

Moim zdaniem Lisp sam w sobie wygląda dobrze w CV, przynajmniej u tych „pracodawców”, u których chciałbym pracować.

Ludzi od Rails olej, mówienie, że Ruby jest super z powodu X, bo tak mówią ludzie od Rails, to jak mówienie, że PHP jest super, bo masz tysiąc funkcji w głównej przestrzeni nazw.

Otwieranie klas to dla sensownych programistów w Rubym raczej ostateczność, ew. bardzo wygodny hack na bardzo rzadkie okazje – zresztą prawie nigdy nie należy otwierać klas, co najwyżej konkretne obiekty (a i to z caveatem jak wyżej).

Trochę podobnie ze złym wykorzystywaniem mixinów – zły kod można napisać w każdym języku, pracujesz w PHP to sam dobrze wiesz najlepiej.

Z argumentem pracowym nie podyskutuję, zresztą rozumiem, że jest dealbreakerem, więc nie za bardzo jest nad czym dywagować. :)

stronger

A fakt, widziałem "Lisp an advantage" gdy startowałem do Xilinksa. Ok, odkładam opowieści Railsowców między starodruki. I tak to jest, że przyjdzie jeden rozsądny koder i spali moje argumenty swoim zdrowym podejściem do sprawy ;-)

Teamon, natomiast co do mixinów - mam diametralnie odmienne zdanie. Lepiej ich nie mieć niż mieć, nawet gdyby unikać stosowania. Cena, którą musisz zapłacić za stosowanie interfejsów i/lub kompozycji w miejsce mixinów jest stosunkowo niewielka w porównaniu z tym, ile i jak łatwo możesz coś zepsuć zmieniając samego mixina. Na nieszczęście mixiny są bardzo proste w użyciu, a programiści lubią chadzać na skróty. Również liczba zależności rośnie niebezpiecznie w przypadku użycia mixinów, w przeciwieństwie do tradycyjnego dziedziczenia jednobazowego z interfejsami. Zależności = problemy. Zależności + przeciętnie doświadczenie programiści = duże problemy.

Khorne

qsort (x:xs) = qsort (filter (< x) xs) ++ [x] ++ qsort (filter (>= x) xs)

Takie standardowe 0.03zł ;-)

Shot

No tak, możemy się tak przerzucać – naiwne kolorowanie wierzchołka w grafie w Rubym (zakładając GRATR) też wygląda jak poezja:

adjacent_colours = adjacent(vertex).map { |v| vertex_label v }
colour = colour.next while adjacent_colours.include? colour
vertex_label_set vertex, colour

…ale trochę nie o to chodzi.:)

Shot

A w kontekście rozważań językowych: http://avdi.org/devblog/2008/10/13/languages-i-want-to-learn/ :)

Eluś

A widziałeś gdzieś rekurencję w funkcjach anonimowych? :)
http://pastie.org/291645

stronger

Eluś, śliczne. Domknięcia rulezują.

Atsd, antydomknięcia (czyli coś spieprzonego ponad wszelkie wyobrażenie) to z całą pewnością domena PHP. Gdy wywołamy statycznie metodę niestatyczną PHP nie dość, że na to zezwoli to jeszcze za $this uzna obiekt, z którego wywołanie zostało wykonane. W efekcie np. metoda save() DAO jest wywoływana na obiekcie kontrolera, co prowadzi do nonsensownego komunikatu o błędzie.

Shot

„Gdy wywołamy statycznie metodę niestatyczną PHP nie dość, że na to zezwoli” – taki język, panie kolego. No i na serio to się powinno się kodować przy E_STRICT, który to wyłapie – tylko jaki procent bibliotek jest wtedy używalny? ;]

Jajcuś

Domknięcia, przynajmniej takie jak w Ruby, jakoś nigdy do mnie nie trafiały. Czytałem ileś wyjaśnień Rubystów czemu one są lepsze od wszystkiego co daje Python i jakoś żadne mnie nie przekonało.
Anonimowych funkcji nie lubię (poza najprostszymi przypadkami typu x -> 2*x). Niepotrzebnie zaciemniają kod. W Pythonie można zrobić zwykle to samo, pisząc troszkę więcej, za pomocą zwykłej, zagnieżdżonej funkcji. Jedyny minus to kolejny ząbek zagnieżdżenia kodu.
A wywołanie "statyczne" metody niestatycznej bardzo fajnie jest rozwiązane w Pythonie. Można to robić (w Pythonie mało jest rzeczy "nie, bo nie!"), ale trzeba odpowiedni obiekt podać jawnie jako pierwszy argument. Co ciekawsze, to jest jedna z niewielu operacji w których Python sprawdza typ argumentu (nie pozwoli wywołać takiej metody z obiektem innej klasy jako 'self').

stronger

Jajcuś, bluźnisz heretyku! ;-)
Serio, nie bardzo potrafię sobie wyobrazić dlaczego ktoś chciałby statycznie wywoływać metodę niestatyczną. Jeśli taka potrzeba istnieje to metoda najpewniej powinna zostać zadeklarowana jako statyczna.
Domknięcia rzeczywiście mają sens głównie w Javascripcie, ale taka jest specyfika domeny tego języka. Środowisko webowe po stronie klienta (mod_js na razie pomijamy) jest bardzo hmm.. żywe, ale jednocześnie nietrwałe.
Co do funkcji anonimowych, świetnie sprawdzają się jako proste callbacki, np. przy renderowaniu tabel. Czasem renderery są tak proste, że "public function xxxx()" zajmuje więcej miejsca niż ciało funkcji.

Jajcuś

Źle się chyba zrozumieliśmy. Przez "statyczne wywołanie" rozumiem tylko wywołanie inne niż przez obiekt.metoda. Metoda dalej nie działa jak statyczna, bo operuje na konkretnym obiekcie, tyle że sama metoda jest pobrana nie z tego obiektu, a z klasy. Zresztą, to standardowy sposób na wywołanie metody z klasy nadrzędnej. Po prostu metodę niestatyczną można przetwarzać nawet gdy nie jest przywiązana do konkretnego obiektu, byle jej przy wywołaniu odpowiedni obiekt podać.
Właściwie "klasa.metoda_niestatyczna" (unbound method) to inny obiekt niż "instancja.metoda_niestatyczna" (bound method).
A i statyczne metody są w pythonie różnego rodzaju: staticmethod i classmethod. Pierwsza jest po prostu funkcją, druga jest związana z klasą tak jak zwykła metoda jest związana z instancją (ma coś w rodzaju "this", które wskazuje odpowiednią klasę). "classmethod" jest bliższym odpowiednikiem statycznych metod z Javy, ale w wielu wypadkach wystarcza "staticmethod".

Shot

@stronger: „Domknięcia rzeczywiście mają sens głównie w Javascripcie” – no nie wiem, w Rubym one są wszędzie i trochę sobie bez nich nie wyobrażam choćby najprostszych iteracji.

A z rzeczy wygodnych:

module Enumerable

def every_pair
every_pair_with_indices do |a, b, i, j|
yield a, b
end
end

def every_pair_with_indices
each_with_index do |a, i|
each_with_index do |b, j|
yield a, b, i, j if i < j
end
end
end

def parallel &block
case ArtDecomp::Conf.processes
when 1 then map &block
else forkoff ArtDecomp::Conf.processes, &block
end
end

end

Pierwsze i drugie pozwala mi na

>> [1,2,3,4].every_pair { |a,b| p [a,b] }
[1, 2]
[1, 3]
[1, 4]
[2, 3]
[2, 4]
[3, 4]

Trzecie zaś mówi, że w zależności od konfiguracji rzeczy typu

[1,2,3,4].parallel { |e| cośtam e }

wykonają się mniej lub bardziej równolegle (gem forkoff).

Shot

Czy ja miałem pisać o domknięciach, a napisałem o blokach? A mówili, żeby nie pisać do internetu o piątej nad ranem…

@Jajcuś: „Źle się chyba zrozumieliśmy. Przez «statyczne wywołanie» rozumiem tylko wywołanie inne niż przez obiekt.metoda. Metoda dalej nie działa jak statyczna, bo operuje na konkretnym obiekcie, tyle że sama metoda jest pobrana nie z tego obiektu, a z klasy.” – my niestety w PHP przez „statyczne wywołanie” rozumiemy wywołanie metody „obiektowej” bez obiektu, tylko przez podanie klasy. Tak, niestety wolno i działa i tylko rzuca (prawie-zawsze-ignorowane) ostrzeżenie.

stronger

No wiesz co, a ja tu siedzę i męczę manuale do Rubyego, żeby Ci coś o tych closures odpisać ;-) 5 rano? Gdzie to, GNU/York?

Jajcuś, Pythonowe statyczne wywołania to zupełnie inny świat. Dodam tylko że ponieważ stosuję trzyenterowe odstępy pomiędzy metodami, to przy omyłkowym statycznym wywołaniu komunikat błędu w PHP dość często wskazuje na pustą linijkę. Zdziwienie widzącego to poraz pierwszy - bezcenne.

Shot

GNU/Ess-Eff. W weekend był http://badcamp.net/ a teraz idę właśnie rękami machać na http://civicrm.org/node/445 :)

Your turn:

nick:
and?:
www (if any):
Wpisz kod:code
Najpiękniejszy kawałek kodu jaki widziałem javascript closures domknięcia