Wyrażenia w Rubim

(4 komentarze)

W kategoriach: Ruby , Ruby tutorial , Techblog / 15 kwietnia 2007 [21:08:02]

Tagi technorati:

W języku Ruby niemal każda konstrukcja, z którą możemy się zetknąć, to wyrażenie - a to zwraca, jak wiemy, określoną wartość. Taka właściwość daje szereg dodatkowych możliwości - ale i obciąża programistę, który niekiedy musi przez to nieco zmienić swoje podejście do kodu i algorytmów. Nic nie stoi na przeszkodzie, by pisać w Rubim z klasyczną manierą, znaną z języków średniego poziomu - ale czy jest sens marnować tak potężne właściwości języka?

Przyjrzyjmy się więc bliżej niektórym rodzajom wyrażeń.

Przypisania

Wykonajmy parę przypisań w interaktywnej konsoli Rubiego. Uznałem, że pokazanie dokładnego przebiegu sesji irb-a powoduje, ze kod traci na czytelności, dlatego wstawiam go w "czystej" postaci, a wartości zwracane przez dane wyrażenie zaznaczam w komentarzach.

a = 1                     # => 1
s = "kot"                 # => "kot"
arr = [1, 3.5, "tekst"]   # => [1, 3.5, "tekst"]

Widzimy, że już samo przypisanie zwraca wartość. Wykorzystać to możemy w sposób oczywisty:

a = b = 3                 # => 3
                          # Wynik:  a = 3, b = 3
x = (y = 2 + 5) + 1       # => 8
                          # Wynik: x = 8, y = 7

Przypisanie tego rodzaju polega na przypisaniu obiektowi referencji do zmiennej bądź stałej. Kiedy zagłębimy się w programowanie obiektowe w Rubim, poznamy drugi rodzaj, który tak naprawdę jest metodą, odwołującą się do np. atrybutu klasy czy indeksu kolekcji.

Przypisania równoległe

a, b = 2, 1         # [2, 1]
                    # Wynik: a = 2, b = 1

Okazało się, że operacja przypisywania "jednym ciurkiem" zwraca tablicę. Przyjrzyjmy się bliżej tej właściwości:

a = [1, 2 ,3 ,4, 5]   # => [1, 2, 3, 4, 5]
b, c, *d = a          # => [1, 2, 3, 4, 5]
                      # Wynik: b = 1, c = 2,
                      # d = [3, 4, 5]

Taka operacja powoduje przypisanie pierwszemu elementowi lewej strony pierwszy element tablicy, drugiemu - drugi, a ostatniemu, poprzedzonemu gwiazką, całą resztę. Prawda, że ciekawe?

Zamiana wartości dwóch zmiennych

W klasycznym podejściu do programowania angażujemy w tę operację trzecią, tymczasową zmienną. W Rubim sprawa wygląda dużo prościej (i bardziej czytelnie):

a, b = b, a

++

W Rubim nie mamy operatora ++, znanego z wielu innych języków programowania. Zamiast niego stosujmy +=, niekoniecznie z wartością 1 :-)

Natura wyrażeń w Rubim

Każda jednostkowa operacja zwraca wartość, która jest obiektem, a ten posiada metody, które wykonać, otrzymując kolejną wartość-obiekt, którą...

[1, 2, 3, 2, 1, 2, 3, 5, 8].uniq.sort.reverse   # => [8, 5, 3, 2, 1]

Powyższy kod wyrzuca duplikaty z tablicy, następnie ją sortuje, by ostatecznie odwrócić kolejność jej elementów.

Uwaga na zera i puste Stringi!

Na tym bardzo łatwo wpaść, jeśli przyzwyczajeni jesteśmy do definicji fałszu wg. takich języków jak PHP czy C. Okazuje się, że fałszem w Rubim jest

  • nil
  • false

i nic więcej. Zapamiętajmy więc, że ani zero, ani pusty String nie są fałszem. Bo nie są - one przecież istnieją! :-)

if 0 
  puts "Zero jest prawdą!"
end
a = ""
if a
  puts "Pusty String też!"
end

Operatory logiczne

Znane nam operatory and, or oraz not posiadają w Rubim szereg ciekawych właściwości.

Operator and oraz && (różniące się tylko priorytetem - and ma niższy) jest dwuargumentowy:

wyr1 && wyr2

lub też

wyr1 and wyr2

Działa następująco:

  • sprawdza, czy wyr1 jest prawdą - jeśli nie, zwraca wyr1 i kończy działanie; wyr2 jest wtedy pomijane i w ogóle nie jest wykonywane
  • jeśli wyr1 jest prawdą, sprawdza, czy wyr2 jest prawdą; jeśli jest - zwraca wyr2
  • jeśli jednak nie jest - również zwraca wyr2

Brzmi trochę skomplikowanie, jak na zwykły operator logiczny, który wielu osobom znany jest z zer i jedynek. Niech parę przykładów rozjaśni całą sytuację:

false and nil     # => false
nil and false     # => nil
false and 1       # => false
1 and nil         # => nil
1 and false       # => false
1 and "tekst"     # => "tekst"
"tekst" and 1     # => 1
"aaa" and "bbb"   # => "bbb"

Zarówno sposób użycia operatorów or oraz ||jak i ich priorytet, wygląda dokładnie tak samo, jak w przypadku and. Działanie operatora or przedstawia się następująco:

  • jeśli wyr1 jest prawdą, zwraca wyr1 i kończy działanie; wyr2 nie jest wtedy w ogóle wykonywane
  • jeśli wyr1 jest fałszem, sprawdza wyr2; jeśli jest prawdą, zwraca wyr2
  • jeśli jednak jest fałszem, również zwraca wyr2

Parę przykładów:

true or false     # => true
true or "a"       # => true
"a" or 5          # => "a"
5 or "a"          # => 5
false or nil      # => nil
nil or false      # => false

Działanie jednoargumentowego operatora not oraz ! jest dużo prostsze: zwraca fałsz, gdy argument jest prawdą, i prawdę, gdy argument jest fałszem. Tak jak poprzednio, not ma priorytet niższy niz !. Oto parę przykładów:

not false     # => true
not nil       # => true
not 4         # => false
not 3.14      # => false

Zwróćmy uwagę, że not zwraca jedynie wartości true oraz false. Zanegowany String daje false, ale nie jest to operacja zalecana (wyskakuje warning), dlatego nie będziemy jej wykonywać.

Operator ||=

Działa w dość ciekawy sposób, skracając nieco zapis pewnych instrukcji.

cart ||= Cart.new

Kod ten stworzy nowy koszyk, ale tylko wtedy, gdy jeszcze takowego nie stworzyliśmy (czyli: jeśli cart jest fałszem).

Operatory porównania

Większą część znamy z innych języków programowania, dlatego ich działanie przedstawione będzie w skrócie:

  • ==, <, >, <=,>= nie wymaga komentarza
  • == posiada swoje zaprzeczenie !=
  • <=>, zwany ogólnym operatorem porównania, zwraca -1, 0 1, gdy wartość lewego operandu jest odpowiednio: mniejsza, równa, większa od wartości drugiego operandu; służy do implementacji wyrażenia case
  • =~ sprawdza dopasowanie wzorca wyrażeń regularnych
  • eql? zwraca prawdę, jeżli oba operandy są tej samej wartości oraz tego samego typu
  • equal? zwraca prawdę, jeśli pierwszy operand jest dokładnie tym samym, co drugi - czyli czy posiada to samo ID
  • wszystkie wymienione wyżej operatory (poza !~ oraz !=, które są konwertowane w czasie wykonywania kodu odpowiednio na !(a == b) oraz !(a =~ b)) to metody, które można nadpisywać

Jak używać operatorów eql? i equal?

(2.0).eql?(2)       # => false
str = "tekst"
str.eql? "tekst"    # => true; zwrócmy uwagę, że nie trzeba 
                    # używać nawiasów
str2 = str
str.equal? str2     # true
str.object_id       # 24327300
str2.object_id      # 24327300

Widać tutaj, że = jest przypisaniem referencji, a nie faktycznym podstawieniem wartości. Może to czasem powodować trudne do wykrycia błędy. Użycie str2 = str.dup spowoduje przypisanie str2 wartości str, ale będą to różne obiekty (różne object_id => equal? zwróci false).

Wyrażenia regularne

W zasadzie jest to temat nie tylko na osobny wpis, ale i na osobną książkę. Tutaj przedstawiony będzie sam fakt ich istnienia :-).

Ruby ma wbudowaną obsługę tego potężnego narzędzia do pracy z tekstem - jest to element jego natywnej składni, a nie bibliotek. Dopasowanie do wzorca sprawdzamy za pomocą operatora =~, a same wyrażenia definiujemy jako sekwencję odpowiednich znaków między ukośnikami: /regexp/. A jak to wygląda w praktyce?

if str = gets =~ /R(uby|ails)/
  puts "Ha! Wiesz co dobre!"
else
  puts "Buuu!"
end

To nawet nie jest smaczek tego, co oferuje nam Ruby i jego wyrażenia regularne. Program wczytuje ze standardowego wejścia tekst. Jeśli zawiera on słowo Ruby lub Rails, wypisze odpowiedni tekst pochwalny.

Podsumowanie

Wygląd i charakter wyrażeń jest jedną z cech rozpoznawczych języka Ruby. Zachęcam do zapoznania się z nimi w sposób bardziej zaawansowany, bo tylko wtedy wykorzystamy cały potencjał drzemiący w tym języku.

Komentarze: Skocz na dół, na górę

  1. Mi na początku trochę brakowało postinkrementacji (chyba tak to ++ się nazywa) i z przyzwyczajenia klepałem te dwa plusiki. Ale wystarczy raz, drugi się zapomnieć, a potem już człowiek zapamiętuje, że plusików nie ma.

  2. Post- albo preinkrementacja. :)

    Mi też brakuje tych plusików. I przede wszystkim fałszywego zera. Przyzwyczajenie bierze górę nad rozsądkiem. :P

  3. Zawsze można powiedzieć ,,przyzwyczajenie wrogiem postępu” ;-)

  4. Można. Ale patrząc na wypowiedzi (i kod) programistów Rubiego, widzę pewien entuzjazm i wręcz demonstracyjne używanie konstrukcji, które świadczą o potędze tego języka. Z takim podejściem trudno o zastój i tkwienie w archaicznych zapisach kodu. I to jest właśnie piękne w Rubim i jego społeczności :-).

Dodaj komentarz na temat

Zanim skomentujesz...

W komentarzach działają znaczniki Textile.
Zastrzegam sobie prawo do edycji Twojego komentarza tylko i wyłącznie w celach estetycznych (naprawienie źle wstawionego kodu, itp). Nie zmieniam ich treści, ortografii, interpunkcji. Jeśli odczuwasz potrzebę edycji swojego komentarza, skontaktuj się ze mną, a zdziałamy co trzeba.