Ruby/Domknięcia i obiekty procedurowe: Różnice pomiędzy wersjami
Usunięta treść Dodana treść
Szymon wro (dyskusja | edycje) Nie podano opisu zmian |
Szymon wro (dyskusja | edycje) |
||
Linia 1:
==
=== Domknięcia ===
Ruby 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 (<tt>{}</tt>), zaraz za wywołaniem metody:
<source lang="ruby">
3.times { print "Bla" } #=> BlaBlaBla
</source>
Domknięcia dłuższe zapisujemy w bloku <tt>do ... end</tt>
<source lang="ruby">
i = 0
3.times do
print
i += 2
end
#=> 024
</source>
Obsługa bloku przekazanego do funkcji odbywa się poprzez słowo kluczowe <tt>yield</tt>, które przekazuje sterowanie do bloku. Spójrzmy na przykład metody <tt>powtorz</tt>.
<source lang="ruby">
while ilosc > 0
yield # tu przekazujemy sterowanie do domkniecia
ilosc -= 1
end
powtorz(3) { print "Bla"} #=> BlaBlaBla
</source>
Po zakończeniu wykonywania przekazanego bloku sterowanie wraca z powrotem do metody. Dzięki słowu kluczowemu <tt>yield</tt> możemy również przekazywać do bloku obiekty:
<source lang="ruby">
def powtorz(ilosc)
while ilosc > 0
yield ilosc
ilosc -= 1
end
end
</source>
Aby użyć wartości przekazanej do bloku stosujemy identyfikator ujęty w znaki <tt>|</tt>:
<source lang="ruby">
powtorz(3) { |n| print "#{n}.Bla " } #=> 3. Bla 2. Bla 1.Bla
</source>
Co jednak, gdy używamy <tt>yield</tt>, a do metody nie przekazaliśmy żadnego bloku? Aby uchronić się przed wystąpieniem wyjątku używamy metody <tt>block_given?</tt>, która zwraca <tt>true</tt>, gdy blok został przekazany.
<source lang="ruby">
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
</source>
=== Obiekty procedurowe ===
Bloki można zamienić w łatwy sposób na obiekty (są to obiekty klasy <tt>Proc</tt>). Można użyć do w tym celu słów kluczowych <tt>lambda</tt> lub <tt>proc</tt>, z czego '''zalecane''' jest to pierwsze. Poniższy kod utworzy dwa obiekty procedurowe:
<source lang="ruby">
hej = lambda { print "Hej" }
witaj = proc do
puts "Witaj!"
end
</source>
Aby wykonać dany blok zawarty w obiekcie procedurowym (wywołać go) należy użyć metody <tt>call</tt>:
<source lang="ruby">
hej.call #=> Hej
witaj.call #=> Witaj!
</source>
W wywołaniu <tt>call</tt> możemy również przekazać parametry do bloku:
<source lang="ruby">
drukuj = lambda { |tekst| print tekst }
drukuj.call("Hop hop!") #=> Hop hop!
</source>
Obiekty procedurowe mogą być, jak każde inne obiekty, przekazywane jako parametry. Możemy zdefiniować alternatywną metodę <tt>powtorz</tt> która bedzie wykorzystywać lambdę przekazaną jako parametr. Rozważmy poniższy przykład:
<source lang="ruby">
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
</source>
Jak widzimy w ostatniej linii, obiekty lambda mogą być anonimowe (nie nadajemy im żadnej nazwy). Więcej o tym, co to są obiekty anonimowe dowiesz się w [[Ruby/Klasy|rozdziale o klasach]]. Natomiast w [[Ruby/Zmienne lokalne|rozdziale o zmiennych lokalnych]] zobaczysz, że obiekty procedurowe i domknięcia zachowują kontekst (stan zmiennych lokalnych) w jakim zostały wywołane.
=== Różnice między lambdą a <tt>Proc.new</tt> ===
Obiekty procedurowe można również tworzyć używając konstrukcji <tt>Proc.new</tt>. Bardziej szczegółowo omówimy tę konstrukcję w [[Ruby/Klasy|rozdziale]] dotyczącym klas, tutaj jedynie powiemy o pewnych różnicach pomiędzy lambdami a obiektami utworzonymi za pomocą <tt>Proc.new</tt>.
Surowe obiekty <tt>Proc</tt> (ang. ''raw procs''), czyli utworzone poprzez <tt>Proc.new</tt>, posiadają jedną niedogodność: użycie instrukcji <tt>return</tt> 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 <tt>Proc</tt>.
<source lang="ruby">
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
</source>
Wywołany <tt>proc1</tt> zwraca jedynie wartość, nie wypisze żadnego tekstu. Odmiennie działa <tt>proc2</tt> - tutaj <tt>return</tt> powoduje, że sama lambda zwraca wartość, do której można się odwołać w dalszej części bloku, w którym utworzono lambdę.
<noinclude>
{{ProstaNawigacja|spis=Ruby|poprzart=Ruby/Struktury sterujące|poprz=Struktury sterujące|nastart=Ruby/Iteratory|nast=Iteratory}}
|