2014-11-08 9 views
5
class Foo 
    def self.run(n,code) 
    foo = self.new(n) 
    @env = foo.instance_eval{ binding } 
    @env.eval(code) 
    end 
    def initialize(n) 
    @n = n 
    end 
end 

Foo.run(42, "p @n, defined? foo") 
#=> 42 
#=> "local-variable" 

Das Beispielprogramm bestimmt wird im Rahmen eines Foo Instanz beliebigen Code auszuwerten. Es tut das, aber die Bindung ist mit den lokalen Variablen aus der code Methode "verschmutzt". Ich möchte nicht, dass foo, n oder code für den Evaluierungscode sichtbar sind. Die gewünschte Ausgabe ist:Erstellen blank Bindung oben im Rahmen eines Objekts

#=> 42 
#=> nil 

Wie kann ich eine Bindung ist, dass (a) im Rahmen der Objektinstanz, aber (b) frei von irgendwelchen lokalen Variablen?


Der Grund, dass ich eine Bindung statt nur instance_eval(code) mit erschaffe, dass in den real usage ich keep the binding around for later usage, muß die lokalen Variablen in ihr geschaffen zu bewahren.

Antwort

4

so ähnlich? oder habe ich hier etwas Wichtiges vermisst?

class Foo 
    attr_reader :b 

    def initialize(n) 
    @n = n 
    @b = binding 
    end 

    def self.run(n, code) 
    foo = self.new(n) 
    foo.b.eval(code) 
    end 
end 

Foo.run(42, "p @n, defined?(foo)") 
# 42 
# nil 

oder nach unten bewegen weiter noch weniger Kontext

class Foo 
    def initialize(n) 
    @n = n 
    end 

    def b 
    @b ||= binding 
    end 

    def self.run(n, code) 
    foo = self.new(n) 
    foo.b.eval(code) 
    end 
end 

Foo.run(42, "p @n, defined?(foo), defined?(n)") 
# 42 
# nil 
# nil 
+0

Sie haben einige der lokalen Variablen entfernt, aber nicht alle: 'Definiert? (N)' ist '" local-variable "' weil Sie 'binding' genannt haben. Es ist sauberer, aber es ist nicht leer. – Phrogz

+0

ist die aktualisierte Version leer genug? – phoet

+0

Ja, die aktualisierte Version funktioniert. Vielen Dank! Beachten Sie jedoch, dass bei jedem Aufruf von "b" eine neue leere Bindung erstellt wird. Wenn dies das ist, was Sie wollen, ist die Antwort perfekt. Wenn Sie stattdessen - wie ich - die gleiche Bindung beibehalten möchten, um die lokalen Variablen von jedem Lauf zu akkumulieren, müssten Sie etwas wie "def b; @b || = Bindung; Ende.Ich gebe Ihnen die Akzeptanz, weil die Verwendung der Instanz als Speicher eleganter ist als die Verwendung eines separaten Objekts (wie in meiner Antwort); Auf andere Weise erzwingt dies eine neue Instanzvariable auf Ihrem Objekt, die Sie möglicherweise nicht haben möchten. – Phrogz

1

Antwort haben:

module BlankBinding 
    def self.for(object) 
    @object = object 
    create 
    end 
    def self.create 
    @object.instance_eval{ binding } 
    end 
end 

Beschreibung:

Um eine Bindung zu bekommen, keine lokale varia Sie müssen binding in einem Bereich aufrufen, in dem es keine gibt. Der Aufruf einer Methode setzt die lokalen Variablen zurück, also müssen wir das tun. Allerdings, wenn wir etwas tun, wie folgt aus:

def blank_binding_for(obj) 
    obj.instance_eval{ binding } 
end 

... die resultierende Bindung wird eine obj lokale Variable haben. Sie können diese Tatsache wie so verbergen:

def blank_binding_for(_) 
    _.instance_eval{ binding }.tap{ |b| b.eval("_=nil") } 
end 

... aber diese entfernt nur den Wert der lokalen Variablen. (Derzeit gibt es keine Methode remove_local_variable in Ruby.) Dies ist ausreichend, wenn Sie die Bindung an einem Ort wie IRB oder ripl verwenden wollen, wobei die Variable _ nach jeder Auswertung gesetzt wird und somit über Ihren Schatten läuft.

Wie oben in der Antwort gezeigt, gibt es jedoch eine andere Möglichkeit, einen Wert an eine Methode zu übergeben, und zwar über eine Instanzvariable (oder Klassenvariable oder globale Variable). Da wir instance_eval verwenden, um die self zu unserem Objekt zu verschieben, werden alle Instanzvariablen, die wir erstellen, um die Methode aufzurufen, nicht in der Bindung verfügbar sein.

Verwandte Themen