Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can a Ruby method access the implicit block argument?

The implicit block argument passed to a Ruby method can be executed using yield, or its existence can be checked using block_given?. I'm trying to procify this implicit block to pass it to another method.

Is this possible?

(It's access to the implicit block argument I'm asking about. Replacing this with an explicit argument won't cut it.)

like image 294
ComDubh Avatar asked May 21 '26 04:05

ComDubh


2 Answers

You can procify it, and more importantly give it a name so you can reference it, using the & ampersand unary prefix sigil in the parameter list of the method, like so:

#implicit, anonymous, cannot be referenced:
def foo
  yield 23 if block_given?
end

foo {|i| puts i }
# 23

#explicit, named, can be referenced:
def bar(&blk)
  yield 23 if block_given? # still works

  blk.(42) if blk # but now it also has a name and is a `Proc`

  # since we have the block available as an object, we can inspect it
  p blk.arity, blk.parameters, blk.source_location, blk.binding

  b = blk.binding
  p b.local_variables.map {|var| [var, b.local_variable_get(var)] }.to_h
end

quux = "Hello"

bar { |a, b, c = nil, d: nil, &e| puts a }
# 23
# 42
# 2
# [[:opt, :a], [:opt, :b], [:opt, :c], [:key, :d], [:block, :e]]
# ["(irb)", 24]
# #<Binding:0x00007fb091051308>
# { :quux => "Hello" }

Those are your two choices:

  • implicit, anonymous, not an object
  • explicit, named, Proc

There used to be an undocumented trick that was actually an unintended side-effect of how Proc::new was implemented in MRI: Proc::new did not check whether you passed a block or not, it simply assumed that you passed a block and would take the first block off the top of the internal VM stack. So, if you didn't pass a block to Proc::new, it would actually end up creating a Proc for the implicit block that was passed to the method (since that was the one which just happened to be on the top of the stack).

But, that was never portable, never guaranteed, never worked in all Ruby implementations, and AFAIK no longer works in YARV.

like image 179
Jörg W Mittag Avatar answered May 23 '26 20:05

Jörg W Mittag


You can refer to the block argument via Proc.new. From the docs:

::new may be called without a block only within a method with an attached block, in which case that block is converted to the Proc object.

Example:

def bar
  yield * 2
end

def foo
  bar(&Proc.new)
end

foo(123)
#=> 456

Note that Proc.new raises an ArgumentError when called without passing a block.

like image 29
Stefan Avatar answered May 23 '26 22:05

Stefan