Die Programmiersprache Ruby (German Edition)
lambda {|x,y| x-y } # Differenz zwischen zwei Zahlen berechnen
deviation = difference< 6.8.4 Memoisationsfunktionen
Memoisation ist ein Begriff aus der funktionalen Programmierung für das Caching der Ergebnisse eines Funktionsaufrufs. Wenn eine Funktion bei Übergabe derselben Argumente stets denselben Wert zurückgibt, Grund zu der Annahme besteht, dass dieselben Argumente wiederholt verwendet werden, und wenn die durchgeführte Berechnung recht aufwändig ist, dann kann Memoisation eine nützliche Optimierung sein. Wir können die Memoisation für
Proc
-und
Method
-Objekte mithilfe der folgenden Methode automatisieren:
module Functional
#
# Ein neues Lambda zurückgeben, das die Ergebnisse dieser Funktion
# in einem Cache speichert und die Funktion nur aufruft, wenn neue
# Argumente übergeben werden.
#
def memoize
cache = {} # Ein leerer Cache. Das Lambda hält ihn in seiner Closure fest.
lambda {|*args|
# Beachten Sie, dass der Hash-Schlüssel das gesamte Argument-Array ist!
unless cache.has_key?(args) # Kein Cache-Ergebnis für diese Argumente?
cache[args] = self[*args] # Ergebnis berechnen und cachen
end
cache[args] # Ergebnis aus dem Cache zurückgeben
}
end
# Ein (eher unnötiger) unärer +-Operator zur Memoisation
# Merkhilfe: Der +-Operator heißt "verbessert".
alias +@ memoize # cached_f = +f
end
Hier sehen Sie, wie wir die Methode
memoize
oder den unären
+
-Operator verwenden könnten:
# Eine memoisierte rekursive factorial-Funktion
factorial = lambda {|x| return 1 if x==0; x*factorial[x-1]; }.memoize
# Oder mithilfe der unären +-Syntax
factorial = +lambda {|x| return 1 if x==0; x*factorial[x-1]; }
Beachten Sie, dass die Funktion
factorial
hier eine rekursive Funktion ist. Sie ruft die memoisierte Version ihrer selbst auf, was optimales Caching bewirkt. Es würde nicht so gut funktionieren, wenn Sie eine rekursive, nicht memoisierte Version der Funktion und dann eine separate memoisierte Version von ihr definieren würden:
factorial = lambda {|x| return 1 if x==0; x*factorial[x-1]; }
cached_factorial = +factorial # Rekursive Aufrufe werden nicht gecacht!
6.8.5 Symbole, Methoden und Procs
Es besteht eine enge Verbindung zwischen den Klassen
Symbol
,
Method
und
Proc
. Wir haben bereits die Methode
method
gesehen, die ein
Symbol
-Argument entgegennimmt und ein
Method
-Objekt zurückgibt.
Ruby 1.9 fügt eine praktische
to_proc
-Methode zur Klasse
Symbol
hinzu. Diese Methode erlaubt es, einem Symbol ein
&
voranzustellen und es als Block an einen Iterator zu übergeben. Es wird davon ausgegangen, dass das Symbol eine Methode bezeichnet. Wenn die mit dieser
to_proc
-Methode erstellte
Proc
aufgerufen wird, ruft sie die in ihrem ersten Argument angegebene Methode auf und übergibt alle nachfolgenden Argumente an diese entsprechende Methode. Hier sehen Sie, wie Sie das einsetzen könnten:
# Ein Array von Integern mit der Methode Fixnum.succ inkrementieren
[1,2,3].map(&:succ) # => [2,3,4]
Ohne
Symbol.to_proc
müssten wir ein wenig ausführlicher sein:
[1,2,3].map {|n| n.succ }
Symbol.to_proc
war ursprünglich als Erweiterung für Ruby 1.8 geplant und wird üblicherweise wie folgt implementiert:
class Symbol
def to_proc
lambda {|receiver, *args| receiver.send(self, *args)}
end
end
Diese Implementierung verwendet die Methode
send
(siehe „8.4.3 Methoden aufrufen“ ), um einen Methodennamen durch ein Symbol aufzurufen. Sie können auch folgende Definition verwenden:
class Symbol
def to_proc
lambda {|receiver, *args| receiver.method(self)[*args]}
end
end
Neben
to_proc
können wir einige verwandte und möglicherweise nützliche Helfermethoden definieren. Beginnen wir mit der Klasse
Module
:
class Module
# Zugriff auf Instanzmethoden mit Array-Schreibweise. Gibt UnboundMethod zurück.
alias [] instance_method
end
Hier definieren wir einfach ein Kürzel für die Methode
instance_method
der Klasse
Module
. Denken Sie daran, dass diese Methode ein
UnboundMethod
-Objekt zurückliefert, das nicht aufgerufen werden kann, bis es an eine bestimmte Instanz seiner Klasse gebunden wird. Hier sehen Sie ein Beispiel, das diese neue Schreibweise verwendet (beachten Sie, wie praktisch es ist, eine Klasse mit den Namen ihrer Methoden zu indexieren!):
String[:reverse].bind("hello").call # => "olleh"
Die Bindung einer ungebundenen Methode kann mithilfe desselben Syntaxkürzels auch einfacher erledigt werden:
class UnboundMethod
# Erlaubt [] als Alternative
Weitere Kostenlose Bücher