2015-05-28 4 views
8

Ich versuche, eine Methode alias eine Methode, die Ruby spezielle $& (returns last regex match) verwenden. Ich kann dies manuell tun und es funktioniert:

original = String.instance_method(:sub) 
String.send(:define_method, :sub) do |*args, &block| 
    puts "called" 
    original.bind(self).call(*args, &block) 
end 
"foo".sub(/f/) { $&.upcase } 
    called 
    # => "Foo" 

Allerdings, wenn ich versuche, eine Methode zu schreiben, die das für mich tut, ist es nicht:

def programatic_alias(klass, method_name) 
    original = klass.instance_method(method_name) 
    klass.send(:define_method, method_name) do |*args, &block| 
    puts "called" 
    original.bind(self).call(*args, &block) 
    end 
end 

programatic_alias(String, :sub) 
"foo".sub(/f/) { $&.upcase } 
    called 
    NoMethodError: undefined method `upcase' for nil:NilClass 
    called 
    called 
    called 
    from (irb):19:in `block in irb_binding' 

Es sieht aus wie der globale Zustand betroffen ist durch der Umfang der programatic_alias Methode, aber ich bin mir nicht sicher, ob das ist, was vor sich geht. Die Fragen lauten wie folgt: Wie kann ich den Code String#sub so programmieren, dass er immer noch mit Rubys speziellen globalen Variablen funktioniert?

+0

Welche Version von Ruby sind verwendest du? Für mich mit 2.2.1 funktioniert dein Beispiel. –

+0

Ich benutze Ruby 2.2.2. Sie müssen sicherstellen, dass Sie die Beispiele in verschiedenen Skripts/IRB-Sitzungen ausführen, andernfalls verwenden Sie möglicherweise den zuvor definierten Methodenaliasnamen. Ich habe auf meinem lokalen Rechner 2.2.1 versucht, das Problem zu reproduzieren. – Schneems

+0

Warum würden Sie die globale Variable '$ &' verwenden? – hakcho

Antwort

4

Soweit ich weiß, können Sie das nicht tun. Die docs sagen

Diese globalen Variablen sind thread-lokale und method-lokale Variablen.

Wenn Sie in die rubinQuelle graben, Zugriff auf $& Anrufe last_match_getter, die ihre Daten von rb_backref_get bekommt, die vm_svar_get Anrufe, die (das Überspringen über ein paar interne Methoden) erhält den aktuellen Steuerrahmen und liest die Daten von dort. Keine dieser Daten ist dem ruby ​​api zugänglich - es gibt keine Möglichkeit, diese Daten von einem Frame zu demjenigen zu übertragen, auf den Sie zugreifen möchten.

In Ihrem zweiten Beispiel geschieht der Aufruf der ursprünglichen Methode in Ihrem programatic_alias Methode, und so wird $& in diesem Bereich festgelegt. Aus dem gleichen Grund

'foo'.try(:sub, /f/) {$&.upcase} 

wird auch nicht funktionieren.

Ihr erstes Beispiel funktioniert, weil die Stelle, an der sub aufgerufen wird und die Stelle, an der (innerhalb des Blocks) referenziert wird, im selben Methodenumfang liegen (in diesem Fall die Ruby-Top-Ebene). Ändern Sie ihn auf:

original = String.instance_method(:sub) 
String.send(:define_method, :sub) do |*args, &block| 
    puts "called" 
    original.bind(self).call(*args, &block) 
end 

def x 
    "foo".sub(/f/) { $&.upcase } 
end 

x() 

und $& nicht mehr definiert in dem Block (wenn Sie die Ausnahme von x geworfen fangen Sie an, dass $& wird auf der obersten Ebene festgelegt wird)

+0

Offenbar ist dies ein ziemlich altes (bekanntes) Problem. Hier ist ein Thread, der darüber ab 2009 https://www.ruby-forum.com/topic/198458#1174493 spricht. Nicht annähernd so detailliert wie deine Erklärung, danke nochmal! – Schneems

Verwandte Themen