5 Stimmen

Ruby: Zusammenfassbare Funktionen mit Argumenten

Ich möchte eine Funktion, die den lokalen Zustand in Ruby beibehält. Jedes Mal, wenn ich die Funktion aufrufe, möchte ich ein Ergebnis zurückgeben, das sowohl von einem aufrufenden Argument als auch von dem gespeicherten Zustand der Funktion abhängt. Hier ist ein einfaches Beispiel:

def inc_mult(factor)
  @state ||= 0   # initialize the state the first time.
  @state += 1    # adjust the internal state.
  factor * @state
end

Beachten Sie, dass der Zustand beim ersten Mal initialisiert wird, aber nachfolgende Aufrufe auf den gespeicherten Zustand zugreifen. Dies ist gut, außer dass @state auf den umgebenden Kontext übergreift, was ich nicht möchte.

Wie lässt sich dies am elegantesten umschreiben, so dass @state nicht ausläuft?

(Anmerkung: Mein eigentliches Beispiel ist viel komplizierter, und die Initialisierung des Zustandes ist teuer.)

4voto

Josh Lee Punkte 159535

Sie möchten wahrscheinlich kapseln inc_mult in eine eigene Klasse auslagern, da Sie den Zustand des Objekts getrennt von seinem Inhalt kapseln wollen. Auf diese Weise können Generatoren (die yield Anweisung) arbeiten in Python und C#.

Etwas so Einfaches wie dies würde genügen:

class Foo 
  state = 0 
  define_method(:[]) do |factor|
    state += 1
    factor * state
  end 
end

Philosophisch gesehen denke ich, dass das, was Sie anstreben, unvereinbar ist mit Rubys Auffassung von Methoden als Nachrichten und nicht als Funktionen, die irgendwie für sich alleine stehen können.

2voto

Mike Trpcic Punkte 24627

Funktionen sind zustandslos. Sie sind prozeduraler Code. Klassen enthalten sowohl Zustand als auch prozeduralen Code. Die eleganteste Art, dies zu tun, wäre, dem richtigen Programmierparadigma zu folgen:

Klasse den Zustand zu erhalten
Funktion den Zustand zu manipulieren

Da Sie Ruby verwenden, erscheint es Ihnen vielleicht etwas eleganter, diese Dinge in ein Modul zu packen, das eingebunden werden kann. Das Modul kann den Zustand verwalten, und die Methode könnte einfach über aufgerufen werden:

require 'incmodule'
IncModule::inc_mult(10)

Oder etwas Ähnliches

1voto

Jörg W Mittag Punkte 349574

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

0voto

DigitalRoss Punkte 138823

Es scheint, als könnten Sie einfach eine globale Variable oder eine Klassenvariable in einer anderen Klasse verwenden, was es Ihnen zumindest ermöglichen würde, den unmittelbar umgebenden Kontext zu überspringen.

0voto

severin Punkte 9848

Nun, Sie könnten ein wenig herumspielen... Wie wäre es mit einer Funktion, die sich selbst umschreibt?

def imult(factor)
  state = 1;
  rewrite_with_state(state+1)
  factor*state
end

def rewrite_with_state(state)
  eval "def imult(factor); state = #{state}; rewrite_with_state(#{state+1}); factor*state; end;"
end

Warnung: Dies ist extrem hässlich und sollte nicht in Produktionscode verwendet werden!

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X