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:
== ObiektyDomknięcia i obiekty procedurowe ==
 
=== Domknięcia ===
Dobrze jest móc sformułować odpowiedzi na niespodziewane zdarzenia. Jak się okazuje, najłatwiej to osiągnąć, jeśli możemy przekazywać całe bloki kodu jako argumenty do innych metod, to znaczy, że chcemy tak traktować kod, jakby to były dane.
 
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:
Obiekt procedurowy jest formułowany z użyciem słowa kluczowego <tt>proc</tt>:
 
<source lang="ruby">
<pre>
3.times { print "Bla" } #=> BlaBlaBla
ruby> bla = proc {
</source>
| puts "BLABLABLA!!!"
| }
#<Proc:0x4017357c>
</pre>
 
Domknięcia dłuższe zapisujemy w bloku <tt>do ... end</tt>
To, na co wskazuje <tt>bla</tt> jest obiektem, i jak większość obiektów, posiada zachowanie które może zostać wywołane. Możemy poprosić nasz obiekt aby wykonał je, wywołując jego metodę <tt>call</tt>:
 
<source lang="ruby">
<pre>
i = 0
ruby> bla.call
3.times do
BLABLABLA!!!
print nili
i += 2
</pre>
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>.
A czy można użyć <tt>bla</tt> jako argumentu metody? Oczywiście.
 
<source lang="ruby">
<pre>
ruby> def wywolajpowtorz( p ilosc)
while ilosc > 0
| puts "Przed wywołaniem procedury..."
yield # tu przekazujemy sterowanie do domkniecia
| p.call
ilosc -= 1
| puts "Procedura zakończona."
| end
end
nil
ruby> wywolaj bla
powtorz(3) { print "Bla"} #=> BlaBlaBla
Przed wywołaniem procedury...
</source>
BLABLABLA!!!
Procedura zakończona.
nil
</pre>
 
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:
Metoda <tt>trap</tt> pozwala przypisać dowolną odpowiedź do każdego sygnału.
 
<source lang="ruby">
<pre>
def powtorz(ilosc)
ruby> uchwyt = proc{ puts "nacisnieto ^C." }
while ilosc > 0
#<Proc:0x401730a4>
yield ilosc
ruby> trap "SIGINT", uchwyt
ilosc -= 1
#<Proc:0x401735e0>
end
</pre>
end
</source>
 
Aby użyć wartości przekazanej do bloku stosujemy identyfikator ujęty w znaki <tt>|</tt>:
Normalnie naciśnięcie ^C powoduje wyjście z interpretera. Zamiast tego drukowana jest informacja a interpreter kontynuuje swoje działanie, tak więc nie stracisz pracy, którą robiłeś. (Nie jesteś jednak w pułapce interpretera na wieczność. Wciąż możesz wpisać <tt>exit</tt>.)
 
<source lang="ruby">
I ostatnia uwaga nim przejdziemy do innych tematów: nadawanie obiektowi procedurowemu nazwy przed powiązaniem go z sygnałem nie jest ściśle konieczne. Równoważny anonimowy obiekt procedurowy mógłby wyglądać tak:
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.
<pre>
ruby> trap "SIGINT", proc{ puts "nacisnieto ^C." }
nil
</pre>
 
<source lang="ruby">
lub jeszcze bardziej zwięźle:
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
<pre>
#=> Brak bloku
ruby> trap "SIGINT", 'puts "nacisnieto ^C."'
</source>
nil
</pre>
 
=== Obiekty procedurowe ===
Ta skrócona forma dostarcza pewnej wygody i czytelności podczas pisania małych, anonimowych procedur.
 
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}}