define_method w pętli for

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?