Może mi ktoś wyjaśnić dlaczego te dwa przykłady dają inne wyniki?
class Message
(1..254).each do |i|
define_method "option#{i}" do |arg|
puts "option #{i} arg #{arg} method: #{__method__}"
end
end
end
class Message2
for i in 1..254
define_method "option#{i}" do |arg|
puts "option #{i} arg #{arg} method: #{__method__}"
end
end
end
a = Message.new
a.option3 'trzy'
a.option4 'cztery'
b = Message2.new
b.option3 'trzy'
b.option4 'cztery'
option 3 arg trzy method: option3
option 4 arg cztery method: option4
option 254 arg trzy method: option3
option 254 arg cztery method: option4
Zasięg zmiennej w tych dwóch przypadkach jest różny. Użycie for
powoduje deklarację zmiennej na poziomie kodu wykonywanego przy deklaracji klasy, inaczej niż w pierwszym przypadku, kiedy istnieje ona tylko wewnątrz bloku. Efektywnie kod z drugiej klasy jet równoważny:
class Message
j = 254
(1..254).each do |i|
define_method "option#{i}" do |arg|
puts "option #{j} arg #{arg} method: #{__method__}"
end
end
end
Musimy użyć innej zmiennej, ponieważ each
wewnątrz bloku stworzy odrębną przestrzeń, która przesłoni wcześniejszą deklarację (dla 1.9+, bo #each
w Ruby 1.8 użyje jako iteratora istniejącej już zmiennej). Sama konstrukcja for
ZTCP została celowo utworzona z tą właściwością, aby kopiować mechanizm z innych języków. Analogicznie sytuacja wygląda np. w Pythonie, gdzie często widuje się wykorzystanie wartości zmiennej, zadeklarowanej chwilę wcześniej w pętli (i ustawianej np. w wyniku użycia break
).
Oczywiście taki flow jest bardzo mało czytelny i powinno się go unikać. Jeżeli potrzebujemy zachować ostatnią wartość z poprzedniej pętli, to możemy ją zwyczajnie zwrócić podając parametr do break
, np:
value = (1..100).each do |number|
break number if rand(10) == 0
end
http://graysoftinc.com/early-steps/the-evils-of-the-for-loop
Tylko cały czas mi się wydawało, że żeby mieć dostęp do zmiennej w klasie musi być zadeklarowana jako @@i.
Czyli jeśli dobrze rozumiem zmienna np. “i” zadeklarowana w klasie będzie widoczna w klasie przez cały czas a zmienne klasowe deklarowane z @@ są tylko po to żeby w przypadku zadeklarowania jej w metodzie rozszerzyć jej widoczność na całą klasę?
[quote=“Snickers, post:3, topic:10674, full:true”]
Tylko cały czas mi się wydawało, że żeby mieć dostęp do zmiennej w klasie musi być zadeklarowana jako @@i.Czyli jeśli dobrze rozumiem zmienna np. “i” zadeklarowana w klasie będzie widoczna w klasie przez cały czas a zmienne klasowe deklarowane z @@ są tylko po to żeby w przypadku zadeklarowania jej w metodzie rozszerzyć jej widoczność na całą klasę? [/quote]
Ta zmienna nie jest związana z samą klasą w żaden sposób (nie jest class variable
/ class instance variable
/ instance variable
). W Ruby deklaracja klasy jest wyrażeniem takim samym jak inne konstrukcje - to właśnie dzięki temu możliwe w ogóle jest metaprogramowanie. Mając do dyspozycji obiekt zadeklarowanej klasy lub jego instancję nie można tej zmiennej odczytać lub zmienić, ona istnieje tylko w kontekście deklaracji zawartości klasy.
Użyte define_method
stanowi zwykłe wyrażenie i w pętli jest wykonywane na etapie deklaracji (dlatego nazwa przyjmuje kolejne wartości iteratora). Sam blok przekazany do define_method
zaś stanowi zawartość deklarowanej metody i jest ewalułowany w momencie wywołania metody. W pierwszym przypadku contextem dla definicji metody jest block z #each
, osobny dla każdej metody i tam zmienna posiada kolejną wartość. W drugim przypadku kontekst jest wyżej - treść deklaracji metody, gdzie zmienna jest zawsze ta sama i posiada wartość, która została jej nadana na końcu wykonywania pętli for
.
Dzięki wielkie za wyjaśnienie.
Czy jest jakiś sposób na to, żeby w przypadku for ewaluować wartość zmiennej “i” na etapie deklaracji?