ruby 使用1美元、2美元等,方法定义中的全局变量

kx5bkwkv  于 2023-08-04  发布在  Ruby
关注(0)|答案(5)|浏览(102)

给定以下两段代码:

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

个字符
为什么这两段代码的输出不同?有没有一种方法可以从方法定义的外部将一个块传递给gsub,这样变量$1$2就可以像在方法定义内部给出块一样被求值?

i2byvkas

i2byvkas1#

为什么输出不同?
ruby中的proc有词法作用域。这意味着当它发现一个未定义的变量时,它将在proc是defined的上下文中解析,而不是call。这就解释了代码的行为。
您可以看到块是在regexp之前定义的,这可能会引起混淆。这个问题涉及到一个神奇的ruby变量,它的工作原理与其他变量大不相同。Citing @JörgWMittag
其实很简单:$SAFE行为与您对全局变量的期望不同的原因是它不是全局变量。是个神奇的独角兽。
Ruby中有很多神奇的独角兽,但不幸的是,它们没有很好的文档记录(事实上,根本没有文档记录),因为其他Ruby实现的开发人员很难找到这样的方法。这些东西的行为都不一样,而且(看起来)不一致,它们几乎唯一的两个共同点是,它们看起来像全局变量,但行为却不像。
有些具有局部范围。有些具有线程局部作用域。有些奇迹般的变化,没有人给他们分配。有些语言对解释者来说有着神奇的意义,可以改变语言的行为方式。有些还附加了其他奇怪的语义。
如果你真的想知道$1$2变量是如何工作的,我想你能找到的唯一的“文档”是rubyspec,这是Rubinus的人用艰苦的方式完成的ruby规范。祝你黑客攻击愉快,但要为痛苦做好准备。
有没有一种方法可以将一个块从另一个上下文传递给gsub,其中$1,$2变量的设置是正确的?
你可以通过下面的修改来实现你想要的(但我敢打赌你已经知道了)

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

字符串
我不知道有什么方法可以在运行中更改proc的作用域。但你真的想这么做吗?

h9vpoimq

h9vpoimq2#

$1$2这样的东西就像 LOCAL VARIABLES 一样,尽管它的前导是$。你可以尝试下面的代码来证明这一点:

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

def bar
  $1
end

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

字符串
你的问题是因为proc z是在方法hello之外定义的,所以zmain的上下文中访问$1,但是gsub在方法hello的上下文中设置$1

8yparm6h

8yparm6h3#

这两个版本是不同的,因为$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)

字符串

yuvru6vn

yuvru6vn4#

这是一个解决方法(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年发布)的答案中详细解释。

ecfsfe2w

ecfsfe2w5#

感谢@masa-sakano提供详细的背景答案。
然而,所有现有的答案都不能正确地与gsub一起工作,因为它们只将第一个匹配数据传递给块(直接作为参数,或间接通过绑定),而gsub对字符串中出现的所有模式进行操作。
下面是一个基于@masa-sakano的答案构建的gsub Package 器,它可以正确工作。在为gsub的每次迭代设置当前匹配数据后,它在另一个块中调用给定的blk

def gsub_wrapper(str, re, blk)
  str.gsub(re) do |m|
    blk.binding.tap do |b|
      b.local_variable_set(:_, $~)
      b.eval("$~=_")
    end

    blk.call(m)
  end
end

字符串
顺便说一句,它也是这个RUBY PUZZLER: GSUB, BLOCKS, AND PROCS的解决方案:

$ cat <<-'EOF'  > ruby-puzzler-gsub-blocks-and-procs.rb
str = "hello world"
upc = Proc.new {|m| $1.upcase}

puts str.gsub(/([aeiou])/, &upc)
puts str.gsub(/(\w)/, &upc)

def doit(str, re, blk)
  puts str.gsub(re, &blk)
end

doit "hello world", /([aeiou])/, upc
doit "hello world", /(\w)/, upc

def gsub_wrapper(str, re, blk)
  str.gsub(re) do |m|
    blk.binding.tap do |b|
      b.local_variable_set(:_, $~)
      b.eval("$~=_")
    end

    blk.call(m)
  end
end

puts gsub_wrapper(str, /([aeiou])/, upc)
puts gsub_wrapper(str, /(\w)/, upc)
EOF

$ ruby ruby-puzzler-gsub-blocks-and-procs.rb
hEllO wOrld
HELLO WORLD
hDllD wDrld
DDDDD DDDDD
hEllO wOrld
HELLO WORLD

相关问题