简体   繁体   中英

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.)

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.

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.

Take a look at this answer . In your case it would be something like:

def outer
  wrapper = lambda { |something|
    p 'Do something crazy in this wrapper'
    yield(something)
  }
  other_method(&wrapper)
end

def other_method
  yield(5)
end

outer { |x| puts x + 3 }

With that you get:

"Do something crazy in this wrapper"
8
=> nil

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM