2013-08-31 4 views
7

die folgenden beiden Teile des Codes Gegeben:

def hello(z) 
    "hello".gsub(/(o)/, &z) 
end 
z = proc {|m| p $1} 
hello(z) 
# prints: nil 

def hello 
    z = proc {|m| p $1} 
    "hello".gsub(/(o)/, &z) 
end 
hello 
# prints: "o" 

Warum sind die Ausgänge dieser beiden Teile des Codes anders? Gibt es eine Möglichkeit, einen Block von außerhalb der Methodendefinition an gsub zu übergeben, so dass die Variablen $1, $2 auf die gleiche Weise ausgewertet würden, als ob der Block innerhalb der Methodendefinition angegeben wäre?

Antwort

2

Warum ist der Ausgang anders?

Ein Proc in Ruby hat lexikalischen Geltungsbereich. Das heißt, wenn eine Variable gefunden wird, die nicht definiert ist, wird sie in dem Kontext aufgelöst, in dem die Prozedur definiert, nicht genannt wurde. Dies erklärt das Verhalten Ihres Codes.

Sie können sehen, dass der Block vor der Regexp definiert ist, und dies kann zu Verwirrung führen. Das Problem beinhaltet eine magische Ruby-Variable, und es funktioniert ganz anders als andere Variablen. Citing @JörgWMittag

Es ist ziemlich einfach, wirklich: der Grund, warum $ SAFE verhält sich nicht wie man es von einer globalen Variablen erwarten ist, weil es nicht eine globale Variable ist. Es ist ein magisches Einhorn dingamajiggy.

Es gibt durchaus ein paar jener magische Einhorn thingamajiggies in Ruby, und sie sind leider nicht sehr gut dokumentiert (nicht dokumentiert, in der Tat), wie die Entwickler der alternativen Ruby-Implementierungen auf die harte Tour herausgefunden. Diese Thinga-Jiggies verhalten sich alle unterschiedlich und (scheinbar) inkonsistent, und so ziemlich die einzigen beiden Dinge, die sie gemeinsam haben, ist, dass sie wie globale Variablen aussehen, sich aber nicht wie diese verhalten.

Einige haben lokalen Anwendungsbereich. Einige haben thread-lokalen Umfang. Manche verändern sich auf magische Weise, ohne dass jemand ihnen etwas zuschreibt. Einige haben eine magische Bedeutung für den Interpreter und ändern, wie sich die Sprache verhält. Einige haben andere seltsame Semantiken an sich.

Wenn Sie wirklich nach oben sind genau das zu finden, wie die $1 und $2 Variablen arbeiten, gehe ich davon aus, die nur „Dokumentation“ finden Sie ist rubyspec, dass eine Spezifikation für Rubin ist die harte Art und Weise von den Leuten Rubinus getan. Habt ein nettes Hacken, aber seid auf den Schmerz vorbereitet.


Gibt es eine Möglichkeit, einen Block, um von einem anderen Zusammenhang mit $ 1, $ 2 Variablen Setup den richtigen Weg zu gsub?

Sie erreichen, was Sie mit dieser nach entsprechenden Änderungen wollen (aber ich wette, dass Sie bereits wissen, dass)

require 'pp' 
def hello(z) 
    #z = proc {|m| pp $1} 
    "hello".gsub(/(o)/, &z) 
end 
z = proc {|m| pp m} 
hello(z) 

Ich bin mir nicht bewusst einen Weg, um den Umfang eines proc on the fly zu ändern . Aber würdest du das wirklich tun wollen?

+0

Auch in diesem Code ' 'hello'.gsub (/ (e) /) {setzt $ 1}' Proc.new vor gsub genannt wird: dies ist, wie jeder Programmiersprache Arbeit - Block wie andere das gleiche Argument ist. Es muss gebaut werden, bevor es irgendwo hinkommt. So dont Ich denke, dass Ihre Antwort etwas –

+0

@BogdanGusiev erklärt 'Proc.new' wird aufgerufen, bevor' gsub', aber der Block nicht. Es wird nur geparst. Ein Block ist kein Argument. – sawa

+1

Ja, wird der Block nicht vor 'in beiden Code-Varianten gsub' genannt. So ist die Differenz die Anzahl der Anrufe Block passieren zu gsub: dass 1 sein könnte oder 2. Aber es ist immer noch gleich nie 0 '$ 1 'Variable nie in dem Kontext, in der' Z' definiert ist. –

1

Die beiden Versionen unterscheiden sich, da die Variable $1 thread-lokal und method-lokal ist.Im ersten Beispiel existiert $1 nur im Block außerhalb der hello Methode. Im zweiten Beispiel existiert $1innerhalb der hello Methode.

Es gibt keine Möglichkeit, $ 1 in einem Block an gsub außerhalb der Methodendefinition zu übergeben.

Beachten Sie, dass gsub das Match-String in den Block geht, arbeiten z = proc { |m| pp m } wird so nur solange Ihr regulärer Ausdruck nur das ganze Spiel enthält. Sobald Ihr regulärer Ausdruck etwas anderes als die gewünschte Referenz enthält, haben Sie kein Glück.

Zum Beispiel "hello".gsub(/l(o)/) { |m| m } =>hello, weil der ganze Match-String an den Block übergeben wurde.

Ange "hello".gsub(/l(o)/) { |m| $1 } =>helo, weil die l, die abgestimmt wurde durch den Block wird verworfen, alles, was wir interessiert sind, ist das erfasste o.

Meine Lösung match der reguläre Ausdruck ist, dann passieren die MatchData Objekt in den Block:

require 'pp' 

def hello(z) 
    string = "hello" 
    regex = /(o)/ 

    m = string.match(regex) 
    string.gsub(regex, z.call(m)) 
end 

z = proc { |m| pp m[1] } 
pp hello(z) 
1

Dinge wie $1, $2 wirkt wie lokale Variablen trotz seiner $ führt. Sie können den Code unten versuchen, dies zu beweisen:

def foo 
    /(hell)o/ =~ 'hello' 
    $1 
end 

def bar 
    $1 
end 

foo #=> "hell" 
bar #=> nil 

Ihr Problem ist, weil die proc z außerhalb der Methode definiert ist hello, so z greift die $1 im Zusammenhang mit main, aber gsub setzt die $1 im Rahmen der Methode hello.