I have the following code:
def call_block
Proc.new.call
my_local_proc = Proc.new { Proc.new.call }
my_local_proc.call
end
call_block { p 'block' }
The output is:
block
block
Can someone explain to me how Proc.new found the block I passed to call_block? I guess that Proc.new just searches for the closest block and that it`s implemented entirely in C++.
And I have another question: Can something like this be achieve using only ruby? I mean, can I write a method such that, if not block has been given, takes the block that was passed to the method calling it. Something like:
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 }
Proc.new
will use the method's block if called without one inside a method with one attached. This is documented behavior.
To find out how YARV does it, let's read the source code. Specifically, the proc_new
function:
block_pointer = rb_vm_control_frame_block_ptr(control_frame_pointer);
This line retrieves a pointer to the block associated with the current control frame.
I believe these control frames implement Ruby's stack. We are currently inside the Proc.new
control frame, so this would retrieve the pointer to the block given to the method.
if (block_pointer != NULL) {
/* block found */
} else {
/* block not found... */
}
If the pointer isn't NULL
, then Proc.new
was passed a block explicitly. What if the pointer is NULL
, though?
/* 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);
We move up on the stack and try to get its block. In other words, we move up to the caller's control frame and try to get its block.
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");
}
Now, if it's not NULL
, then we pretty much succeeded. If it's still NULL
, then we can't create a Proc
, so we raise an ArgumentError
.
The algorithm boils down to this:
Proc.new
was given a block
Source code altered for readability. Visit linked source file on GitHub for the original.