Ruby/Domknięcia i obiekty procedurowe
Domknięcia i obiekty procedurowe
edytujDomknięcia
edytujRuby jest językiem korzystającym w dużym stopniu z domknięć. Domknięcie jest blokiem kodu przekazywanym do metody. Samo w sobie nie jest obiektem. Domknięcie zawierające niewiele instrukcji, które można zapisać w jednej linii zapisujemy pomiędzy nawiasami klamrowymi ({}), zaraz za wywołaniem metody:
3.times { print "Bla" } #=> BlaBlaBla
Domknięcia dłuższe zapisujemy w bloku do ... end
i = 0
3.times do
print i
i += 2
end
#=> 024
Obsługa bloku przekazanego do funkcji odbywa się poprzez słowo kluczowe yield, które przekazuje sterowanie do bloku. Spójrzmy na przykład metody powtorz.
def powtorz(ilosc)
while ilosc > 0
yield # tu przekazujemy sterowanie do domkniecia
ilosc -= 1
end
end
powtorz(3) { print "Bla" } #=> BlaBlaBla
Po zakończeniu wykonywania przekazanego bloku sterowanie wraca z powrotem do metody. Dzięki słowu kluczowemu yield możemy również przekazywać do bloku obiekty:
def powtorz(ilosc)
while ilosc > 0
yield ilosc
ilosc -= 1
end
end
Aby użyć wartości przekazanej do bloku stosujemy identyfikator ujęty w znaki |:
powtorz(3) { |n| print "#{n}.Bla " } #=> 3.Bla 2.Bla 1.Bla
Co jednak, gdy używamy yield, a do metody nie przekazaliśmy żadnego bloku? Aby uchronić się przed wystąpieniem wyjątku używamy metody block_given?, która zwraca true, gdy blok został przekazany.
def powtorz(ilosc)
if block_given?
while ilosc > 0
yield ilosc
ilosc -= 1
end
else
puts "Brak bloku"
end
end
powtorz(3) # nie przekazujemy bloku
#=> Brak bloku
Obiekty procedurowe
edytujBloki można zamienić w łatwy sposób na obiekty (są to obiekty klasy Proc. O tym, czym dokładnie są obiekty i klasy dowiesz się w rozdziale o klasach.) Można użyć w tym celu słów kluczowych lambda lub proc, z czego zalecane jest to pierwsze. Poniższy kod utworzy dwa obiekty procedurowe:
hej = lambda { print "Hej" }
witaj = proc do
puts "Witaj!"
end
Aby wykonać dany blok zawarty w obiekcie procedurowym (wywołać go) należy użyć metody call:
hej.call #=> Hej
witaj.call #=> Witaj!
W wywołaniu call możemy również przekazać parametry do bloku:
drukuj = lambda { |tekst| print tekst }
drukuj.call("Hop hop!") #=> Hop hop!
Obiekty procedurowe mogą być, jak każde inne obiekty, przekazywane jako parametry. Możemy zdefiniować alternatywną metodę powtorz która bedzie wykorzystywać lambdę przekazaną jako parametr. Rozważmy poniższy przykład:
def powtorz(ile, co)
while ile > 0
co.call(ile) # wywołujemy blok "co"
ile -= 1
end
end
l = lambda do |x|
print x
end
powtorz(3, l) #=> 321
powtorz(3, lambda { print "bla" }) #=> blablabla
Jak widzimy w ostatniej linii, obiekty lambda mogą być anonimowe (nie nadajemy im żadnej nazwy). O obiektach anonimowych dowiemy się wkrótce więcej. Natomiast w rozdziale o zmiennych lokalnych zobaczymy, że obiekty procedurowe i domknięcia zachowują kontekst (stan zmiennych lokalnych) w jakim zostały wywołane.
Różnice między lambdą a Proc.new
edytujObiekty procedurowe można również tworzyć używając konstrukcji Proc.new. Bardziej szczegółowo omówimy tę konstrukcję w rozdziale dotyczącym klas. Tutaj jedynie przedstawimy pewne różnice pomiędzy lambdami a obiektami utworzonymi za pomocą Proc.new.
Surowe obiekty Proc (ang. raw procs), czyli utworzone poprzez Proc.new, posiadają jedną niedogodność: użycie instrukcji return powoduje nie tyle wyjście z domknięcia obiektu procedurowego, co wyjście z całego bloku, w którym domknięcie było wywołane. Może to powodować niespodziewane wyniki działania naszych programów, dlatego zaleca się używanie lambd, a nie surowych obiektów Proc.
def proc1
p = Proc.new { return -1 }
p.call
puts "Nikt mnie nie widzi :-("
end
def proc2
p = lambda { return -1 }
puts "Blok zwraca #{p.call}"
end
Wywołany proc1 zwraca jedynie wartość, nie wypisze żadnego tekstu. Odmiennie działa proc2 - tutaj return powoduje, że sama lambda zwraca wartość, do której można się odwołać w dalszej części bloku, w którym utworzono lambdę.