Wyrażenia w Rubim
Posted by Jacek Galanciak on Apr 15 2007
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, zwracawyr1
i kończy działanie;wyr2
jest wtedy pomijane i w ogóle nie jest wykonywane - jeśli
wyr1
jest prawdą, sprawdza, czywyr2
jest prawdą; jeśli jest - zwracawyr2
- 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ą, zwracawyr1
i kończy działanie;wyr2
nie jest wtedy w ogóle wykonywane - jeśli
wyr1
jest fałszem, sprawdzawyr2
; jeśli jest prawdą, zwracawyr2
- 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żeniacase
=~
sprawdza dopasowanie wzorca wyrażeń regularnycheql?
zwraca prawdę, jeżli oba operandy są tej samej wartości oraz tego samego typuequal?
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.