8

给定以下两段代码:

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"

为什么这两段代码的输出不同?有没有办法gsub从方法定义外部传递一个块,以便以与在方法定义内部给出块相同$1$2方式评估变量?

4

4 回答 4

3

为什么输出不一样?

ruby 中的 proc 具有词法范围。这意味着当它找到一个未定义的变量时,它会在 proc 被定义的上下文中被解析,而不是被调用。这解释了您的代码的行为。

您可以看到块是在正则表达式之前定义的,这可能会导致混淆。这个问题涉及一个神奇的 ruby​​ 变量,它的工作方式与其他变量完全不同。引用@JörgWMittag

真的很简单:$SAFE 的行为不像您对全局变量的期望那样的原因是因为它不是全局变量。这是一个神奇的独角兽东西amajiggy。

Ruby 中有很多这样神奇的独角兽 thingamajiggies,不幸的是,它们的文档记录不是很好(实际上根本没有文档记录),因为替代 Ruby 实现的开发人员发现了困难的方式。这些 thingamajiggies 的行为都不同且(似乎)不一致,它们唯一的两个共同点是它们看起来像全局变量,但行为却不像它们。

有些有本地范围。有些具有线程本地范围。有些神奇地改变了,没有任何人分配给他们。有些对解释器具有神奇的意义,并改变了语言的行为方式。有些还附加了其他奇怪的语义。

如果您真的想知道$1and$2变量是如何工作的,我假设您会找到的唯一“文档”是ruby​​spec ,这是由 Rubinus 人艰难完成的 ruby​​ 规范有一个很好的黑客攻击,但要为痛苦做好准备。


有没有办法通过正确的方式设置 $1, $2 变量从另一个上下文将块传递给 gsub?

您可以通过以下修改实现您想要的(但我敢打赌您已经知道了)

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

我不知道有一种方法可以即时更改 proc 的范围。但你真的想这样做吗?

于 2013-08-31T17:01:36.133 回答
2

LOCAL VARIABLES之类的东西$1,尽管它处于领先地位。您可以尝试下面的代码来证明这一点:$2$

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

def bar
  $1
end

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

您的问题是因为 procz是在方法之外定义的hello,所以在 的上下文中z访问,但在方法的上下文中设置。$1maingsub$1hello

于 2014-05-09T07:58:12.200 回答
1

这两个版本是不同的,因为$1变量是线程局部的和方法局部的。在第一个例子中,$1只存在于方法的块中hello。在第二个示例中,$1存在于方法内部hello

无法从方法定义之外将块中的 $1 传递给 gsub。

请注意,gsub将匹配字符串传递到块中,因此z = proc { |m| pp m }只有在您的正则表达式仅包含整个匹配项时才会起作用。一旦您的正则表达式包含您想要的引用以外的任何内容,您就不走运了。

例如,"hello".gsub(/l(o)/) { |m| m }=> hello,因为整个匹配字符串被传递到块。

"hello".gsub(/l(o)/) { |m| $1 }=> helo,因为l匹配的 被块丢弃,所以我们感兴趣的只是捕获的o

我的解决方案是match正则表达式,然后将MatchData对象传递到块中:

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)
于 2013-08-31T17:41:52.127 回答
1

这是一种解决方法(Ruby 2)。给定的 Proc 的z行为与给定的块完全相同String#gsub

def hello(z)
  "hello".match /(o)/  # Sets $1, $2, ...
  z.binding.tap do |b|
    b.local_variable_set(:_, $~)
    b.eval("$~=_")
  end
  "hello".gsub(/(o)/, &z)
end
z = proc {|m| p $1}
hello(z)
# prints: "o"

在这个问题“如何将 Regexp.last_match 传递给 Ruby 中的块”(发布于 2018 年)的答案中详细解释了背景 。

于 2018-10-12T15:53:12.507 回答