Ich möchte eine Funktion die den lokalen Zustand in Ruby beibehält.
Das Wort "Funktion" sollte sofort ein großes, fettes, rot blinkendes Warnzeichen auslösen, dass Sie die falsche Programmiersprache verwenden. Wenn Sie Funktionen wollen, sollten Sie eine funktionale Programmiersprache verwenden, keine objektorientierte. In einer funktionalen Programmiersprache schließen Funktionen in der Regel über ihre lexikalische Umgebung, was das, was Sie zu tun versuchen, absolut trivial macht:
var state;
function incMult(factor) {
if (state === undefined) {
state = 0;
}
state += 1;
return factor * state;
}
print(incMult(2)); // => 2
print(incMult(2)); // => 4
print(incMult(2)); // => 6
Dieses Beispiel ist in ECMAScript geschrieben, aber es sieht mehr oder weniger gleich aus in tout funktionale Programmiersprache.
[Anmerkung: Ich bin mir bewusst, dass dies kein sehr gutes Beispiel ist, weil ECMAScript eigentlich auch eine objektorientierte Sprache ist und weil es eine gebrochene Gültigkeitsbereichssemantik hat, die praktisch bedeutet, dass state
auch in diesem Fall undicht. In einer Sprache mit korrekter Scope-Semantik (und in ein paar Jahren wird ECMAScript eine davon sein), wird dies wie vorgesehen funktionieren. Ich habe ECMAScript hauptsächlich wegen seiner vertrauten Syntax verwendet, nicht als Beispiel für eine gute funktionale Sprache].
Dies ist die Art und Weise, wie Zustände in funktionalen Sprachen gekapselt werden, seit, nun ja, seit es funktionale Sprachen gibt, bis hin zum Lambda-Kalkül.
In den 1960er Jahren stellten jedoch einige kluge Köpfe fest, dass dieses Muster sehr häufig vorkommt, und sie beschlossen, dass dieses Muster also gemeinsam, dass sie ein eigenes Sprachfeature verdient. Und so wurde die Objekt wurde geboren.
In einer objektorientierten Sprache würden Sie also anstelle von funktionalen Abschlüssen Objekte verwenden, um den Zustand zu kapseln. Wie Sie vielleicht bemerkt haben, schließen Methoden in Ruby nicht über ihre lexikalische Umgebung ab, im Gegensatz zu Funktionen in funktionalen Programmiersprachen. Und genau das ist der Grund: Die Kapselung des Zustands wird auf andere Weise erreicht.
In Ruby würden Sie also ein Objekt wie dieses verwenden:
inc_mult = Object.new
def inc_mult.call(factor)
@state ||= 0
@state += 1
factor * @state
end
p inc_mult.(2) # => 2
p inc_mult.(2) # => 4
p inc_mult.(2) # => 6
[Nebenbemerkung: Diese 1:1-Entsprechung ist es, was funktionale Programmierer meinen, wenn sie sagen, "Objekte sind nur die Abschlüsse eines armen Mannes". Natürlich kontern objektorientierte Programmierer gewöhnlich mit "Schließungen sind nur die Objekte eines armen Mannes". Und das Lustige ist, dass beide Recht haben und es keinem von ihnen bewusst ist].
Der Vollständigkeit halber möchte ich darauf hinweisen, dass Methoden zwar nicht über ihre lexikalische Umgebung geschlossen werden, es aber in Ruby ein Konstrukt gibt, das hace : Blöcke. (Interessanterweise sind Blöcke keine Objekte.) Und da Sie Methoden mit Blöcken definieren können, können Sie auch Methoden definieren, die Abschlüsse sind:
foo = Object.new
state = nil
foo.define_singleton_method :inc_mult do |factor|
state ||= 0
state += 1
factor * state
end
p foo.inc_mult(2) # => 2
p foo.inc_mult(2) # => 4
p foo.inc_mult(2) # => 6