In Ruby wird ein neuer Sichtbarkeitsbereich für lokale Variablen an mehreren Stellen erstellt. Man muss diese Stellen verstehen und lernen.
- globaler Kontext (main);
- Klassen- (oder Modul-) Definition;
- Methoden-Definition;
Betrachten wir einige Beispiele von der Website ruby-lang.org und einige neue.
Erstellen wir eine Datei example.rb mit dem Code
Globaler Sichtbarkeitsbereich (main)
# Eine globale Variable wird außerhalb von Klassen, Methoden oder Blöcken definiert.
var = 1 # Globale Variable
class Demo
var = 2 # Klassenvariable
def method
var = 3 # Lokale Methode-Variable
puts "in method: var = #{var}"
end
puts "in class: var = #{var}"
end
puts "at top level: var = #{var}"
Demo.new.method
Die Ausführung des Skripts 'ruby example.rb' zeigt uns folgendes Ergebnis:
# in class: var = 2
# at top level: var = 1
# in method: var = 3
Beachten Sie, dass der Code innerhalb der Klassendefinition während seiner Erstellung ausgeführt wird, weshalb die Meldung innerhalb der Klasse sofort erscheint.
Blöcke in Ruby und deren Sichtbarkeit
In Ruby erzeugen Blöcke ({ ... } oder do ... end) fast einen neuen Sichtbarkeitsbereich. Das bedeutet, dass lokale Variablen, die innerhalb eines Blocks definiert sind, normalerweise außerhalb des Blocks nicht zugänglich sind. Es gibt jedoch einige Besonderheiten. Beachten Sie das Wort 'fast', es wird auf der Seite der Ruby-Sprache verwendet und manchmal von Anfängern falsch interpretiert.
Beispiel mit einem Block
a = 0
1.upto(3) do |i|
a += i
b = i * i
end
puts a # => 6
puts b # Es wird ein Fehler auftreten, da b außerhalb des Blocks nicht definiert ist
In diesem Beispiel wird die Variable a, die vor dem Block definiert wurde (Konstruktion do ... end), innerhalb des Blocks modifiziert, und diese Änderungen sind außerhalb des Blocks sichtbar. Auf der anderen Seite ist die Variable b, die innerhalb des Blocks definiert wurde, außerhalb nicht zugänglich.
Blöcke
fast erzeugen einen neuen Sichtbarkeitsbereich, da lokale Variablen, die innerhalb des Blocks definiert sind, von außen nicht zugänglich sind. Wenn jedoch eine Variable bereits im äußeren Sichtbarkeitsbereich existiert, bevor man in den Block eintritt, wird sie auch innerhalb des Blocks verfügbar sein, und Änderungen an ihr bleiben nach dem Verlassen des Blocks erhalten.
Warum "fast"? Das Wort "fast" wird aus mehreren Gründen verwendet (die bereits zuvor beschrieben wurden):
- Variablen, die vor dem Block definiert werden, können innerhalb des Blocks zugänglich und modifiziert werden. Änderungen an ihnen bleiben nach dem Verlassen des Blocks erhalten.
- Variablen, die zum ersten Mal innerhalb des Blocks definiert werden, sind außerhalb des Blocks nicht zugänglich.
Diese Nuancen sind besonders wichtig zu beachten, wenn man mit Threads und asynchronem Code arbeitet, wo die Sichtbarkeit die Zugänglichkeit von Variablen zwischen verschiedenen Teilen des Codes beeinflussen kann.
Betrachten wir ein weiteres Beispiel für ein besseres Verständnis:
x = 10
[1, 2, 3].each do |i|
x += i
y = i * 2
end
puts x # => 16 (die Variable x wird innerhalb des Blocks verändert)
puts y # Es wird ein Fehler auftreten, da y außerhalb des Blocks nicht definiert ist
Der Fehler für 'puts y' wird folgender sein:
(irb):8:in `<main>': undefined local variable or method `y' for main:Object (NameError)
puts y
^
from /Users/user/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/irb-1.13.1/exe/irb:9:in `<top (required)>'
from /Users/user/.rbenv/versions/3.2.1/bin/irb:25:in `load'
from /Users/user/.rbenv/versions/3.2.1/bin/irb:25:in `<main>'
In diesem Beispiel wird die Variable x vor dem Block definiert und innerhalb des Blocks modifiziert. Die Variable y wird innerhalb des Blocks definiert und ist außerhalb nicht zugänglich.
Dies veranschaulicht, wie Blöcke "fast" einen neuen Sichtbarkeitsbereich erzeugen, aber nicht ganz den Zugriff auf Variablen trennen, die bereits im äußeren Sichtbarkeitsbereich existieren.
Beispiel mit Threads
threads = []
["one", "two"].each do |name|
threads << Thread.new do
local_name = name
a = 0
3.times do |i|
Thread.pass
a += i
puts "#{local_name}: #{a}"
end
end
end
threads.each { |t| t.join }
Das Ergebnis in meinem Fall (das Ergebnis hängt von der Ausführung von Thread.pass, dem Betriebssystem und der CPU ab):
one: 0
two: 0
one: 1
one: 3
two: 1
two: 3
Wir erstellen ein leeres Array threads, in dem wir alle erstellten Threads speichern werden. Die each-Schleife durchläuft das Array der Strings ["one", "two"], wobei jedes Element nacheinander in der Variablen name verfügbar ist. Für jedes Element des Arrays wird ein neuer Thread mit Thread.new erstellt. Der Code innerhalb des Blocks do ... end wird im Kontext des neuen Threads ausgeführt.
Die Variable local_name erhält den Wert des aktuellen Elements des Arrays name. Dies wird getan, damit jeder Thread seine eigene lokale Kopie der Variable name hat. Dann wird eine lokale Variable a mit dem Startwert 0 erstellt. Anschließend läuft die Schleife. Die Schleife wird dreimal ausgeführt. Bei jeder Iteration haben wir folgende Aktionen:
- Thread.pass wird ausgeführt, was anderen Threads erlaubt, ausgeführt zu werden.
- Die Variable a wird um den Wert i erhöht.
- Der Wert local_name und der aktuelle Wert a werden ausgegeben.
threads.each { |t| t.join }
Der join-Befehl zwingt den Hauptthread, auf den Abschluss jedes der erstellten Threads zu warten. Dies ist notwendig, damit alle Threads ihre Arbeit beenden, bevor das Programm (Skript) beendet wird.
Kontrollstrukturen, Methoden und deren Sichtbarkeiten
Im Folgenden werden Beispiele für Kontrollstrukturen und Methoden zur Veranschaulichung der Funktionsweise von Sichtbarkeiten aufgeführt. Ruby hat eine Vielzahl von Kontrollstrukturen und Methoden, die den Fluss der Programmausführung steuern.
if /
elsif /
else
if condition
# code
elsif another_condition
# anderer code
else
# anderer code
end
unless condition
# code
end
case variable
when value1
# code
when value2
# anderer code
else
# anderer code
end
while condition
# code
end
until condition
# code
end
for element in collection
# code
end
loop do
# code
break if condition
end
begin /
rescue /
ensure /
else
begin
# code
rescue SomeException => e
# Ausnahmebehandlung
ensure
# code, der immer ausgeführt wird
else
# code, der ausgeführt wird, wenn keine Ausnahme auftritt
end
for i in 0..5
retry if i > 2
puts "i: #{i}"
end
begin
# code
rescue
retry
end
for i in 0..5
next if i < 3
puts "i: #{i}"
end
for i in 0..5
break if i > 2
puts "i: #{i}"
end
Kontrollstrukturen (if, while, for, usw.) erzeugen keinen neuen Sichtbarkeitsbereich, sodass lokale Variablen innerhalb von ihnen im umgebenden Kontext verfügbar sind.
Beispiele für Methoden:
times
5.times do |i|
puts i
end
1.upto(5) do |i|
puts i
end
5.downto(1) do |i|
puts i
end
0.step(10, 2) do |i|
puts i
end
[1, 2, 3].each do |element|
puts element
end
result = [1, 2, 3].map do |element|
element * 2
end
puts result
result = [1, 2, 3, 4, 5].select do |element|
element.even?
end
puts result
result = [1, 2, 3, 4, 5].reject do |element|
element.even?
end
puts result
result = [1, 2, 3, 4, 5].find do |element|
element.even?
end
puts result
sum = [1, 2, 3, 4, 5].inject(0) do |accumulator, element|
accumulator + element
end
puts sum
Methoden (times, each, usw.) akzeptieren häufig Blöcke, die einen neuen Sichtbarkeitsbereich für Variablen schaffen können, die innerhalb des Blocks definiert sind.
Ich habe absichtlich viele Beispiele für Kontrollstrukturen und Methoden hinzugefügt, um zu zeigen, dass man hier leicht einen Fehler machen und ein Problem mit der Sichtbarkeit bekommen kann. Das Hauptziel ist nicht, sich alle Methoden zu merken, sondern den Unterschied zwischen Kontrollstrukturen und Methoden zu verstehen. Dieses Wissen wird helfen,
Fehler zu debuggen in potenziell problematischen Stellen im Code.
Um anschaulich zu zeigen, wie das alles funktioniert, schreiben wir Tests:
require 'rspec'
RSpec.describe 'Sichtbarkeiten in Ruby' do
context 'Kontrollstrukturen' do
it 'erzeugt einen neuen Sichtbarkeitsbereich mit if/elsif/else' do
if true
var = 1
end
expect(var).to eq(1)
end
it 'erzeugt einen neuen Sichtbarkeitsbereich mit unless' do
unless false
var = 2
end
expect(var).to eq(2)
end
it 'erzeugt einen neuen Sichtbarkeitsbereich mit case/when' do
case 1
when 1
var = 3
end
expect(var).to eq(3)
end
it 'erzeugt einen neuen Sichtbarkeitsbereich mit while' do
i = 0
while i < 1
var = 4
i += 1
end
expect(var).to eq(4)
end
it 'erzeugt einen neuen Sichtbarkeitsbereich mit until' do
i = 0
until i > 0
var = 5
i += 1
end
expect(var).to eq(5)
end
it 'erzeugt einen neuen Sichtbarkeitsbereich mit for' do
for i in 0..0
var = 6
end
expect(var).to eq(6)
end
it 'erzeugt einen neuen Sichtbarkeitsbereich mit loop' do
var = nil
loop do
var = 7
break
end
expect(var).to eq(7)
end
it 'behandelt eine Ausnahme mit begin/rescue/ensure' do
var = 0
begin
raise 'Fehler'
rescue
var = 1
ensure
var += 2
end
expect(var).to eq(3)
end
it 'behandelt eine Ausnahme mit begin/rescue/else/ensure' do
var = 0
begin
var += 1
rescue
var += 2
else
var += 3
ensure
var += 4
end
expect(var).to eq(8)
end
it 'wiederholt die Ausführung mit redo' do
var = 0
i = 0
for i in 0..5
if i < 2
var = i
break if i == 1 # Vermeidet eine Endlosschleife
end
end
expect(var).to eq(1)
end
it 'wiederholt die Ausführung mit retry' do
var = 0
attempts = 0
begin
raise 'Fehler' if attempts < 1
rescue
attempts += 1
retry if attempts < 2
else
var = 9
end
expect(var).to eq(9)
end
it 'überspringt eine Iteration mit next' do
var = []
for i in 0..5
next if i < 3
var << i
end
expect(var).to eq([3, 4, 5])
end
it 'verlässt die Schleife mit break' do
for i in 0..5
break if i > 2
var = i
end
expect(var).to eq(2)
end
end
context 'Methoden' do
it 'erzeugt einen neuen Sichtbarkeitsbereich mit times' do
1.times do
var = 10
end
expect(defined?(var)).to be_nil
end
it 'erzeugt einen neuen Sichtbarkeitsbereich mit upto' do
1.upto(1) do
var = 11
end
expect(defined?(var)).to be_nil
end
it 'erzeugt einen neuen Sichtbarkeitsbereich mit downto' do
1.downto(1) do
var = 12
end
expect(defined?(var)).to be_nil
end
it 'erzeugt einen neuen Sichtbarkeitsbereich mit step' do
0.step(0, 1) do
var = 13
end
expect(defined?(var)).to be_nil
end
it 'erzeugt einen neuen Sichtbarkeitsbereich mit each' do
[1].each do
var = 14
end
expect(defined?(var)).to be_nil
end
it 'erzeugt einen neuen Sichtbarkeitsbereich mit map' do
[1].map do
var = 15
end
expect(defined?(var)).to be_nil
end
it 'erzeugt einen neuen Sichtbarkeitsbereich mit select' do
[1].select do
var = 16
end
expect(defined?(var)).to be_nil
end
it 'erzeugt einen neuen Sichtbarkeitsbereich mit reject' do
[1].reject do
var = 17
end
expect(defined?(var)).to be_nil
end
it 'erzeugt einen neuen Sichtbarkeitsbereich mit find' do
[1].find do
var = 18
end
expect(defined?(var)).to be_nil
end
it 'erzeugt einen neuen Sichtbarkeitsbereich mit inject/reduce' do
[1].inject(0) do |acc, _|
var = 19
end
expect(defined?(var)).to be_nil
end
end
end
Alle Tests wurden erfolgreich ausgeführt:
Finished in 0.06456 seconds (files took 0.26825 seconds to load)
23 examples, 0 failures