给定以下两段代码:
def hello(z) "hello".gsub(/(o)/, &z) end z = proc {|m| p $1} hello(z) # prints: nil
个字符为什么这两段代码的输出不同?有没有一种方法可以从方法定义的外部将一个块传递给gsub,这样变量$1,$2就可以像在方法定义内部给出块一样被求值?
gsub
$1
$2
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的作用域。但你真的想这么做吗?
h9vpoimq2#
像$1,$2这样的东西就像 LOCAL VARIABLES 一样,尽管它的前导是$。你可以尝试下面的代码来证明这一点:
$
def foo /(hell)o/ =~ 'hello' $1 end def bar $1 end foo #=> "hell" bar #=> nil
字符串你的问题是因为proc z是在方法hello之外定义的,所以z在main的上下文中访问$1,但是gsub在方法hello的上下文中设置$1。
z
hello
main
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对象传递到块中:
z = proc { |m| pp m }
"hello".gsub(/l(o)/) { |m| m }
"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)
字符串
yuvru6vn4#
这是一个解决方法(Ruby 2)。给定的Proc z的行为与给定给String#gsub的块完全相同。
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年发布)的答案中详细解释。
ecfsfe2w5#
感谢@masa-sakano提供详细的背景答案。然而,所有现有的答案都不能正确地与gsub一起工作,因为它们只将第一个匹配数据传递给块(直接作为参数,或间接通过绑定),而gsub对字符串中出现的所有模式进行操作。下面是一个基于@masa-sakano的答案构建的gsub Package 器,它可以正确工作。在为gsub的每次迭代设置当前匹配数据后,它在另一个块中调用给定的blk:
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
型
5条答案
按热度按时间i2byvkas1#
为什么输出不同?
ruby中的proc有词法作用域。这意味着当它发现一个未定义的变量时,它将在proc是defined的上下文中解析,而不是call。这就解释了代码的行为。
您可以看到块是在regexp之前定义的,这可能会引起混淆。这个问题涉及到一个神奇的ruby变量,它的工作原理与其他变量大不相同。Citing @JörgWMittag
其实很简单:$SAFE行为与您对全局变量的期望不同的原因是它不是全局变量。是个神奇的独角兽。
Ruby中有很多神奇的独角兽,但不幸的是,它们没有很好的文档记录(事实上,根本没有文档记录),因为其他Ruby实现的开发人员很难找到这样的方法。这些东西的行为都不一样,而且(看起来)不一致,它们几乎唯一的两个共同点是,它们看起来像全局变量,但行为却不像。
有些具有局部范围。有些具有线程局部作用域。有些奇迹般的变化,没有人给他们分配。有些语言对解释者来说有着神奇的意义,可以改变语言的行为方式。有些还附加了其他奇怪的语义。
如果你真的想知道
$1
和$2
变量是如何工作的,我想你能找到的唯一的“文档”是rubyspec,这是Rubinus的人用艰苦的方式完成的ruby规范。祝你黑客攻击愉快,但要为痛苦做好准备。有没有一种方法可以将一个块从另一个上下文传递给gsub,其中$1,$2变量的设置是正确的?
你可以通过下面的修改来实现你想要的(但我敢打赌你已经知道了)
字符串
我不知道有什么方法可以在运行中更改proc的作用域。但你真的想这么做吗?
h9vpoimq2#
像
$1
,$2
这样的东西就像 LOCAL VARIABLES 一样,尽管它的前导是$
。你可以尝试下面的代码来证明这一点:字符串
你的问题是因为proc
z
是在方法hello
之外定义的,所以z
在main
的上下文中访问$1
,但是gsub
在方法hello
的上下文中设置$1
。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
对象传递到块中:字符串
yuvru6vn4#
这是一个解决方法(Ruby 2)。给定的Proc
z
的行为与给定给String#gsub
的块完全相同。字符串
背景在这个问题“如何将Regexp.last_match传递给Ruby中的块”(2018年发布)的答案中详细解释。
ecfsfe2w5#
感谢@masa-sakano提供详细的背景答案。
然而,所有现有的答案都不能正确地与
gsub
一起工作,因为它们只将第一个匹配数据传递给块(直接作为参数,或间接通过绑定),而gsub
对字符串中出现的所有模式进行操作。下面是一个基于@masa-sakano的答案构建的
gsub
Package 器,它可以正确工作。在为gsub
的每次迭代设置当前匹配数据后,它在另一个块中调用给定的blk
:字符串
顺便说一句,它也是这个RUBY PUZZLER: GSUB, BLOCKS, AND PROCS的解决方案:
型