Ruby/Zmienne lokalne

Zmienne lokalne edytuj

Zmienna lokalna posiada nazwę zaczynającą się małą literą lub znakiem podkreślenia (_). Zmienne lokalne nie posiadają, w przeciwieństwie do zmiennych globalnych i zmiennych instancji, wartości nil przed inicjalizacją:

irb(main):001:0> $a
=> nil
irb(main):002:0> @a
=> nil
irb(main):003:0> a
NameError: undefined local variable or method `a' for main:Object
        from (irb):3

Pierwsze przypisanie do zmiennej lokalnej odgrywa rolę jakby deklaracji. Jeżeli odwołasz się do niezainicjalizowanej zmiennej lokalnej, interpreter Rubiego nie będzie pewny, czy odwołujesz się do prawdziwej zmiennej. Możesz, dla przykładu, zrobić błąd w nazwie metody. Dlatego zobaczyłeś raczej ogólną informację o błędzie.

Zazwyczaj zasięgiem zmiennej lokalnej jest jedno z poniższych:

  • proc { ... }
  • lambda { ... }
  • loop { ... }
  • def ... end
  • class ... end
  • module ... end
  • cały skrypt (chyba że zastosowano jedno z powyższych)

Użyty w następnym przykładzie defined? jest operatorem, który sprawdza czy identyfikator został zdefiniowany. Zwraca on opis identyfikatora, jeśli jest on zdefiniowany lub, w przeciwnym razie, nil. Jak widzisz, zasięg zmiennej b jest ograniczony lokalnie do pętli. Gdy pętla zostaje przerwana zmienna b jest niezdefiniowana.

a = 44
puts defined?(a) #=> local-variable

loop do
  b=45
  break
end
puts defined?(b) #=> nil

Obiekty procedurowe, które żyją w pewnym zakresie widoczności współdzielą tylko te zmienne lokalne, które również należą do tego zakresu. Tutaj, zmienna lokalna a jest współdzielona przez main oraz obiekty procedurowe l1 i l2:

a = nil
l1 = lambda {|n| a=n}
l2 = lambda {a}
l1.call(5)
puts a       #=> 5
puts l2.call #=> 5

Zauważ, że nie można pominąć a = nil na początku. To przypisanie zapewnia, że zasięg zmiennej a obejmie l1 i l2. Inaczej l1 i l2 utworzyłyby swoje własne zmienne lokalne a, i rezultatem wywołania l2 byłby błąd "undefined local variable or method" (niezdefiniowana zmienna lokalna lub metoda). Moglibyśmy użyć a = 0, ale użycie nil jest pewną uprzejmością wobec przyszłych czytelników naszego kodu. Pokazuje naprawdę jasno, że tylko ustanawiamy zakres, ponieważ wartość przypisana do zmiennej nie niesie żadnego specjalnego znaczenia (nil).

Domknięcia a kontekst edytuj

W rozdziale o domknięciach i obiektach procedurowych wspomnieliśmy, że domknięcia i obiekty procedurowe zachowują kontekst używanych zmiennych lokalnych. Jest to bardzo potężna zaleta: współdzielone zmienne lokalne pozostają poprawne nawet wtedy, gdy przekazane są poza pierwotny zakres.

def pudelko
  zawartosc = nil
  wez = lambda { zawartosc }
  wloz = lambda { |n| zawartosc = n }
  return wez, wloz
end

odczyt, zapis = pudelko

puts odczyt.call   #=> nil
zapis.call(2)
puts  odczyt.call  #=> 2

Ruby jest szczególnie sprytny jeśli chodzi o zakres. W naszym przykładzie ewidentnie widać, że zmienna zawartosc jest współdzielona pomiędzy odczyt i zapis. Możemy również wytworzyć wiele par odczyt-zapis używając metody pudelko zdefiniowanej powyżej. Każda para współdzieli zmienną zawartosc, ale pary nie kolidują ze sobą nawzajem. Dopóki istnieją obiekty procedurowe, zachowane są ich konteksty wywołania wraz z odpowiadającymi im zmiennymi lokalnymi!

odczyt_1, zapis_1 = pudelko
odczyt_2, zapis_2 = pudelko

zapis_1.call(99)
puts odczyt_1.call  #=> 99

# w tym pudelku jeszcze nic nie ma
puts odczyt_2.call  #=> nil

Powyższy kod można by wręcz uważać za lekko perwersyjny zorientowany obiektowo framework. Metoda pudelko odgrywa rolę klasy podczas gdy wez i wloz służą jako metody, natomiast zawartosc jest samotną zmienną instancji. Oczywiście stosowanie właściwego szkieletu klas Rubiego prowadzi do znacznie bardziej czytelnego kodu ;).