Any fool can make things bigger, more complex, and more violent. It takes a touch of genius and a lot of courage to move in the opposite direction. Albert Einstein

o Ruby

Idiomatyczny Ruby

5 komentarzy | Kategorie: Ruby, Techblog, Tips & tricks | trackback
Tagi:

Na wstępie chciałbym Was powitać po dłuższej przerwie. Zleciały wakacje, zleciał wrzesień a ja nie zmusiłem się do napisania ani jednego wpisu. Wynika to z mojego podejścia do pisania. Otóż każdy wpis traktowałem dosyć poważnie i zawsze chciałem napisać coś sensownego co w praktyce oznaczało obszernego (wystarczy spojrzeć na poprzednie wpisy). Napisanie jednego artykułu trwało kilka (a nawet więcej) godzin. W najbliższej przyszłości prawdopodobnie się to zmieni i pojawiać się będą także bardzo krótkie wpisy, ale się zobaczy :). To tyle tytułem wstępu.

Świat idiomów

W dzisiejszym wpisie postaram się przybliżyć Wam temat idiomów (nie mylić z idiotami;)), ze szczególnym uwzględnieniem Rubiego rzecz jasna (niech nie zmyli Was wstęp, to nie jest rozprawka na język polski! :D). Zacznijmy od wstępu teoretycznego. Cytując za Wikipedią:

Idiom, idiomat, idiomatyzm – wyrażenie językowe (najczęściej kilkuwyrazowe) którego znaczenie jest inne niż by to wynikało ze znaczenia poszczególnych wyrazów wchodzących w skład idiomu oraz z reguł językowych.

O idiomach najczęściej wspomina się w kontekście nauki języków obcych (ponieważ stanowią one liczną grupę wyjątków od poznawanych przez uczącego się reguł), stąd też bardzo często stosuje się inną definicję:

Idiom – wyrażenie właściwe tylko danemu językowi, nie dające się dosłownie przetłumaczyć na inny język.

Przykłady polskich idiomów

  • piąte koło u wozu 'osoba lub rzecz zawadzająca'
  • urwanie głowy 'bezładny pośpiech'

Po co nam idiomy? Ponieważ używając ich dostajemy wiele korzyści. Możemy wyrazić w bardzo zwięzły sposób coś, czego opisanie przy użyciu "normalnych" słów zajęłoby nam dłużej ("O wilku mowa" zamiast "Ooo, cześć. Wiesz właśnie o Tobie rozmawialiśmy"). Dają nam także nowy zasób słownictwa, są zastępstwem innych wyrażeń ("Mam tyle co kot napłakał" zamiast "Mam niewiele"). A że profesor Miodek ze mnie marny, na tym poprzestanę.

Wtf?

Czy to co napisałem do tej pory ma coś wspólnego z programowaniem? Gdyby nie miało to nie pisałbym ;). Programując używamy języków programowania. A te jak inne języki rządzą się podobnymi prawami (co nie oznacza, że takimi samymi). Ponieważ idiomy to zlepek innych słów, istnieją także i w językach programowania. Możliwe, że używamy ich nie wiedząc o tym. Dla przykładu, chcąc napisać nieskończoną pętlę w języku C zazwyczaj napiszemy:

while (1) {
  //kod
}

Ale możemy także napisać:

for (;;) {
  //kod
}

Oba rozwiązania są równoważne. To pierwsze rozwiązanie można by określić jako typowe, to drugie jako idiomatyczne, tym bardziej, że pętla for kojarzy się z pętla o konkretnej liczbie kroków, dających się wyznaczyć jeszcze przed uruchomieniem pętli. Ciekawą rzeczą jest tu fakt, że niewprawiony programista może mieć problemy ze zrozumieniem tej drugiej konstrukcji. Nie wygląda ona jak typowa pętla for (for (x = 0; x < 10; x++)), a zlepione średniki mogą wprowadzić irytuację. Zwracam na to uwagę, gdyż znajomość języka nie oznacza automatycznej znajomości idiomów. Odczuć to można na własnej skórze ucząc się języka obcego. Ma to dokładnie takie samo znaczenie w przypadku programowania. Idiomy dają nowe możliwości, ale nic za darmo. Musimy poświęcić im troszkę czasu.

Idiomy Rubiego

Szczerze mówiąc z idiomami (jeśli chodzi o programowanie) spotkałem się dopiero w Rubym. Wydaje mi się, że jest to spowodowane tym iż język ten zwiera takie elementy, które są niespotykane (albo nie są tak bardzo popularne). Do tej pory trochę przynudziłem (nie śpijcie jeszcze;)), teraz przejdę do konkretnych przykładów, porównując różne podejścia do tego samego problemu (pojawią się też inne języki niż Ruby).

Chcąc zadeklarować prostą klasę, z jednym atrybutem moglibyśmy napisać tak (wzorując się np. na Javie, C++):

class SomeClass
  def attr
    @attr
  end

  def attr=(attr)
    @attr = attr
  end
end

Ale oczywiście lepiej jest zrobić tak (chodź w tym wypadku zahaczamy także o tematykę DSL, ale o tym innym razem):

class SomeClass
  attr_accessor :attr
end

Chcąc napisać funkcję (metodę) która przyjmuje dowolną liczbę argumentów w języku C moglibyśmy napisać tak:

#include <stdarg.h>
#include <stdio.h>

void print_nums(int count, ...)
{
  va_list ap;
  int i;

  va_start(ap, count); /* Initialize the argument list. */

  for (i = 0; i < count; i++)
  {
    int num = va_arg(ap, int); /* Get the next argument value. */
    printf("%d\n", num);
  }

  va_end(ap); /* Clean up. */
}

main()
{
  print_nums(3, 102, 5, 689);
}

W Rubym znacznie prościej:

def print_nums(*numbers)
  numbers.each {|n| puts n }
end

print_nums(1, 2, 3, 4)

Chcemy zwrócić lub przypisać nową wartość do zmiennej w zależności od tego czy inna zmienna ma wartość obliczana jest jako prawda lub fałsz.

if myvar
  return myvar
else
  return another_value
end

Lepiej jest jednak napisać:

return myvar || another_value

Chcemy przypisać nową wartość zmiennej, jeśli jej wartość obliczana jest jako fałsz.

if not breakfast
  breakfast = :bacon
end

#albo

breakfast = :bacon unless breakfast

Jednak wprawny programista Rubiego napisze:

breakfast ||= :bacon

Ten sposób jest bardzo często wykorzystywane w metodach zwracających jakąś wartość, która musi wpierw być obliczona.

def current_user
  return @current_user ||= User.find(session[:id]) #uwaga na wyjątek jeśli session[:id] zwróci nil
end

Chcemy do pliku dołączyć pewną część kodu, która zostanie uruchomiona tylko wtedy gdy plik został wywołany bezpośrednio (ruby plik.rb) a nie poprzez require czy load. (W ten sposób można dołączyć np. testy bezpośrednio w kodzie.) Użyjemy zmiennej $0 lub jej aliasu $PROGRAM_NAME.

if $PROGRAM_NAME == __FILE__ #
    do_stuff
end

Niesamowicie idiomatyczne są bloki kodu w Rubym. Oto kilka przykładów. Bardzo często wykonując pewne operacje pracujemy na pewnych zasobach. Zasobem może być w zasadzie cokolwiek. Najbardziej typowym przykładem jest baza, plik, gniazdo (socket). Zasób taki wpierw pozyskujemy, następnie wykonujemy na nim jakieś operacje (zapis danych, odczyt) i oddajemy (dzięki czemu system odzyskuje z powrotem zasoby. Wydawać by się mogło, że nie ma tu nic czego mielibyśmy się obawiać. To na co musimy zawsze zwrócić uwagę to ten 3 (najważniejszy) krok - zwolnienie zasobu. Nawet jeśli w kodzie umieścimy odpowiednie operacje zwalniające, nie mamy gwarancji, że tak się stanie (np. rzucony i nieobsłużony wyjątek już po otwarciu pliku, a przed jego zamknięciem). I tu właśnie należy wręcz skorzystać z następującego rozwiązania:

class Resource
  def self.open(identifier)
    resource = Resource.new(identifier)
    yield resource
  ensure
    resource.close
  end

  def close
    #...
  end
end

Resource.open(identifier) do |resource|
   process(resource)
end

# zasób jest zamknięty, nawet jeśli pojawił się wyjątek

Wygląda znajomo? Powinno :). Takie rozwiązanie zastosowano w klasie File.

File.open("plik.txt", "w") do |file|
  #...
end
# nie martwimy się o zamknięcie pliku

Jeśli chcemy obsłużyć zarówno klasycznie otwarcie zasobu (bierzemy odpowiedzialność za jego zamknięcie) jak i to powyższe napiszemy coś takiego:


def Resource.open(identifier)
 resource = Resource.new( identifier )
 if block_given?
   begin
     yield resource
   ensure
     resource.close
   end
 else
   return resource
 end
end

r = Resource.open(x)
#...
r.close

#lub

Resource.open(x) do |r|
 #...
end

Bloki (nawet jeśli chodzi o sam wygląd w kodzie) bardzo ładnie reprezentują tranzakcje. Spójrz proszę na kod:

Account.transaction(david, mary) do
  david.withdrawal(100)
  mary.deposit(100)
end

Jeśli podczas wywołania tego bloku wystąpi wyjątek, tranzakcja zostanie odwołana. To jest o tyle piękne, że w tradycyjnym podejściu postępowalibyśmy tak: otworzyć trazakcję, wykonać operacje, zamknąć tranzakcję. W tym wypadku nie dość, że omijamy pierwszy i ostatni krok (automatycznie) to jeszcze sam blok w pięknie obrazuje nam znaczenie tranzakcji (albo cały blok, albo nic).

Następny przykład. Chcemy z dwóch liczb wybrać większą (bądź mniejszą) i zwrócić ją. Klasycznie zrobimy tak:

def maximum(a, b)
  if a > b
    return a
  else
    return b
  end
end

Jednakże idiomatycznie będzie tak (a także krócej, czytelniej!):

def maximum(a, b)
  return [a, b].max # czy to w ogóle wymaga komentarza?
end

Takich przykładów można podawać mnóstwo. Na koniec podam przykład idiomu, na który natknąłem się dosyć niedawno, a który bardzo mi się spodobał. Chodzi o sytuację, kiedy chcemy wywołać jakąś metodę i zapamiętać zwróconą wartość. Jeśli wywołanie nie powiedzie się (wyjątek) chcemy wywołać jakąś inną metodę (może być wiele takich metod). Klasycznie zrobilibyśmy to tak:

value = nil
begin
  value = do_something1
rescue => e
  begin
    value = do_something2
  rescue => e
    #...
  end
end

Ale po co tak robić jak można tak:

value = do_something1 rescue do_something2 rescue "sorry"

Idiomów jest znacznie więcej i nie jestem w stanie ich tu podać wszystkich :). Ważne jest to, żeby znać te najczęściej stosowane, gdyż ułatwia nam to wspólne porozumiewanie się. Na koniec podaję dodatkowe źródła, do których przeglądnięcia bardzo zachęcam :). Czekam też na Wasze ulubione idomy, niekoniecznie te Rubinowe.

Zasoby

http://www.rubygarden.org/ruby/page/show/RubyIdioms
http://renaud.waldura.com/doc/ruby/idioms.shtml
http://podcast.sdruby.com/2007/1/16/episode-014-ruby-idioms-part-1

Jeśli spodobał Ci się wpis to może umieścisz ten blog w swoim czytniku RSS?

Komentarze

1. avatar icon Musk napisał(a) 13 Paź 2007 o godz. 20:16:

Przydatny wpis, ciekawe przykłady :). Jesteś w moim czytniku.

2. avatar icon Radarek napisał(a) 13 Paź 2007 o godz. 20:21:

Przykładów jest znacznie więcej – zaglądnij do linków, które podałem na końcu :).

3. avatar icon T.M.S napisał(a) 29 Paź 2007 o godz. 12:38:

Ciekawy post. Good job !

4. avatar icon kotosha napisał(a) 12 Lis 2007 o godz. 13:44:

Naprawdę przydatny artykuł. Piszę pracę licencjacką o Ruby i często zaglądam na Twoją stronę. Tak trzymać:)

5. avatar icon Radarek napisał(a) 12 Lis 2007 o godz. 13:46:

@kotosha, dzięki za słowa uznania :). Ruby na tym blogu króluje i nie zamierzam tego zmieniać.

Dodaj coś od siebie

Możesz korzystać ze składni Textile.

Pola oznaczone * są wymagane.

Proszę o dodawanie komentarzy związanych z tematem postu, sprawy osobiste proszę załatwiać przez maila bądź gg.

Zastrzegam sobie prawo do moderacji komentarzy (edycja, usuwanie).