Die Programmiersprache Ruby (German Edition)
werden ermittelt, wenn das Lambda oder die Proc ausgeführt wird.
Als Beispiel definiert der nachfolgende Code eine Methode, die zwei Lambdas zurückgibt. Da die Lambdas im selben Gültigkeitsbereich definiert werden, haben sie gemeinsamen Zugriff auf die Variablen in diesem Gültigkeitsbereich. Wenn ein Lambda den Wert einer gemeinsamen Variablen ändert, steht der neue Wert dem anderen Lambda zur Verfügung:
# Ein Lambda-Paar mit gemeinsamem Zugriff auf eine lokale Variable zurückgeben
def accessor_pair(initialValue=nil)
value = initialValue # Eine lokale Variable, die die Lambdas gemeinsam haben
getter = lambda { value } # Den Wert der lokalen Variablen zurückgeben
setter = lambda {|x| value = x } # Den Wert des lokalen Variablen ändern
return getter,setter # Ein Lambda-Paar an die aufrufende Stelle
# zurückgeben
end
getX, setX = accessor_pair(0) # accessor-Lambdas für Anfangswert 0 erzeugen
puts getX[] # Gibt 0 aus. Beachten Sie die eckigen Klammern statt call.
setX[10] # Den Wert durch die eine Closure ändern
puts getX[] # Gibt 10 aus. Der Wert ist durch die andere sichtbar.
Die Tatsache, dass im selben Gültigkeitsbereich erstellte Lambdas gemeinsamen Zugriff auf Variablen haben, kann sowohl ein Feature als auch eine Fehlerquelle sein. Jedes Mal, wenn Sie eine Methode haben, die mehr als eine Closure zurückgibt, sollten Sie besonders auf die Variablen aufpassen, die sie verwenden. Betrachten Sie den folgenden Code:
# Ein Array von Lambdas zurückgeben, die mit den Argumenten multiplizieren
def multipliers(*args)
x = nil
args.map {|x| lambda {|y| x*y }}
end
double,triple = multipliers(2,3)
puts double.call(2) # Gibt in Ruby 1.8 den Wert 6 aus
Diese
multipliers
-Methode verwendet den Iterator
map
und einen Block, um ein Array von Lambdas zurückzugeben (die innerhalb des Blocks erstellt wurden). In Ruby 1.8 sind Blockargumente nicht immer lokal für den Block (siehe „5.4.3 Blöcke und Gültigkeitsbereiche von Variablen“ ), so dass schließlich alle Lambdas gemeinsamen Zugriff auf
x
besitzen, eine lokale Variable der Methode
multipliers
. Wie oben erwähnt, erhalten Closures nicht den aktuellen Wert der Variablen: Sie erhalten die Variable selbst. Jedes der hier erstellten Lambdas verwendet dieselbe Variable
x
. Diese Variable hat nur einen Wert, und alle zurückgegebenen Lambdas verwenden denselben Wert. Das führt dazu, dass das Lambda, das wir
double
nennen, sein Argument schließlich verdreifacht, anstatt es zu verdoppeln.
In diesem speziellen Code verschwindet des Problem in Ruby 1.9, weil Blockargumente in dieser Version der Sprache stets blocklokal sind. Doch Sie können noch immer jedes Mal in Schwierigkeiten geraten, wenn Sie Lambdas innerhalb einer Schleife erstellen und eine Schleifenvariable (etwa einen Array-Index) innerhalb des Lambda verwenden.
6.6.2 Closures und Bindungen
Die Klasse
Proc
definiert eine Methode namens
binding
. Der Aufruf dieser Methode für eine Proc oder ein Lambda gibt ein
Binding
-Objekt zurück, das die für diese Closure gültigen Bindungen darstellt.
----
Mehr über Bindungen
Wir haben die Bindungen einer Closure besprochen, als seien sie eine einfache Zuordnung von Variablennamen zu Variablenwerten. In Wirklichkeit gehört zu Bindungen mehr als nur Variablen. Sie enthalten alle Informationen, die zum Ausführen einer Methode notwendig sind, etwa den Wert von
self
und den Block, falls vorhanden, der durch ein
yield
aufgerufen würde.
----
Ein
Binding
-Objekt besitzt selbst keine interessanten Methoden, aber es kann als zweites Argument der globalen Funktion
eval
verwendet werden (siehe „8.2 Strings und Blöcke auswerten“ ) und liefert dann einen Kontext, in dem ein String mit Ruby-Code ausgewertet werden soll. In Ruby 1.9 besitzt
Binding
seine eigene
eval
-Methode, die Sie bevorzugt verwenden sollten. (Verwenden Sie
ri
, um mehr über
Kernel.eval
und
Binding.eval
zu erfahren.)
Die Verwendung eines
Binding
-Objekts und der Methode
eval
liefert uns eine Hintertür, durch die wir das Verhalten einer Closure manipulieren können. Werfen Sie noch einmal einen Blick auf diesen weiter oben gezeigten Code:
# Ein Lambda zurückgeben, das das Argument n enthält oder "umschließt"
def multiplier(n)
lambda {|data| data.collect{|x| x*n } }
end
doubler = multiplier(2) # Ein Lambda erhalten, das verdoppeln kann
puts doubler.call([1,2,3]) # Gibt 2,4,6 aus
Nehmen wir nun an, wir wollen das Verhalten von
doubler
ändern:
eval("n=3", doubler.binding) # Oder
Weitere Kostenlose Bücher