我有以下代码:
def call_block
Proc.new.call
my_local_proc = Proc.new { Proc.new.call }
my_local_proc.call
end
call_block { p 'block' }
输出结果为:
block
block
有人可以向我解释Proc.new是如何找到我传递给call_block的块的吗? 我猜Proc.new只搜索最近的块,它完全用C ++实现。
我还有另一个问题:这样的东西只能用红宝石来实现吗?我的意思是, 我可以编写一个方法,如果没有给出块,则获取传递给调用它的方法的块。类似的东西:
def bar
if not block_given?
#use the block that has been given to the caller
end
# some code
end
def foo
bar
end
foo { :block }
答案 0 :(得分:2)
Proc.new
将使用方法的块,如果在没有附加一个方法的情况下调用该方法的块。 This is documented behavior
要了解YARV是如何做到的,让我们阅读源代码。具体来说,proc_new
function:
block_pointer = rb_vm_control_frame_block_ptr(control_frame_pointer);
该行检索指向与当前控制帧关联的块的指针。
我相信这些控制框架实现了Ruby的堆栈。我们当前位于Proc.new
控制框内,因此这将检索指向该方法的块的指针。
if (block_pointer != NULL) {
/* block found */
} else {
/* block not found... */
}
如果指针不是NULL
,则Proc.new
显式传递了一个块。如果指针是 NULL
怎么办?
/* block not found... */
control_frame_pointer = RUBY_VM_PREVIOUS_CONTROL_FRAME(control_frame_pointer);
block_pointer = rb_vm_control_frame_block_ptr(control_frame_pointer);
我们向上移动并试图获得阻止。换句话说,我们向上移动到调用者的控制框架并试图阻止它。
if (block_pointer != NULL) {
if (is_lambda) {
rb_warn("tried to create Proc object without a block");
}
} else {
rb_raise(rb_eArgError, "tried to create Proc object without a block");
}
现在,如果它不是NULL
,那么我们几乎成功了。如果仍然 NULL
,那么我们无法创建Proc
,因此我们提出ArgumentError
。
算法归结为:
Proc.new
是否有阻止
为了便于阅读,修改了源代码。访问GitHub上的原始链接源文件。
答案 1 :(得分:1)
如果在没有块的情况下调用Proc.new
,则将传递给方法的块(如果有的话)转换为proc对象。
如果在致电foo
时未传递任何阻止,则会引发ArgumentError
异常,这是合理的,导致block_given?
返回false并尝试使用{{ 1}}没有阻挡。
截至第二个问题,您可以使用Proc.new
表示法通过多种方法传递proc。
它会将给定的块转换为proc并且你可以进一步传递它:
&
同样,def bar &proc
proc.call
end
def foo &proc
bar &proc
end
p foo { :block }
# => :block
方法也可以这样重写:
bar
def bar
yield if block_given?
end
将执行给定的块,因此您不需要将其显式转换为proc,也不需要通过yield
执行它
答案 2 :(得分:0)
来自the manual。
创建一个绑定到当前上下文的新Proc对象。 Proc :: new may 只在带有附加块的方法中被调用而没有块, 在这种情况下,该块将转换为Proc对象。