简体   繁体   中英

Ruby Closures: How to return args and block as a single argument to pass to a method

Suppose I have a method that takes args and a block:

def yield_if_widget(*args, &block)
  if args[0].is_a?(Widget)
    block.call
  end
end

I can call this method with arguments and a block:

yield_if_widget(Widget.new) do
  puts "I like widgets"
end

But what if I have another method that prepares the arguments and the block:

def widget_and_block
  args = [Widget.new]
  block = proc{ puts "I like widgets" }

  [args, block]
end

And I want to be able to pass it directly to the first method:

yield_if_widget(*widget_and_block)

Is this possible? How? Assume that yield_if_widget is defined in a library and monkey-patching it is not an option.

To make ruby understand that the parameter in a call to a method is a block, a commercial and must be put in front of it.

def widget_and_block
  args = [Widget.new]
  block = proc{ puts "I like widgets" }
  [args, block]
end

wab = widget_and_block
#                           ⇓
yield_if_widget(*wab.first, &wab.last)

Oneliner (it won't return what yiw returns):

widget_and_block.tap { |args, cb| yield_if_widget *args, &cb }

UPD Basically, ampersand in method call is used to say “hey, convert this to proc and use as codeblock” to ruby. It's a syntax part of the language, like you have to put array content inside square brackets, not curly. That's why an ampersand should exist in the source code.

On the other hand, whether you were free to modify the yield_if_widget and remove ampersand from parameter list:

-def yield_if_widget(*args, &block)
+def yield_if_widget(*args, block)

the code would work as expected, since the proc instance is passed as the last parameter and calling call method on it is very naturally permitted.

Please also note, that prepending an ampersand to the last parameter to method call forces #to_proc method to be called on it, like in:

[1,2,3].reduce &:+
#⇒ 6

The magic above works because Symbol class has it's own implementation of #to_proc method.

you can't do it in one line (as far as I can tell). You need temporary variables.

given these methods:

def prepare
  return [4, proc { puts "called" }]
end

def run(a, &block)
  puts a
  block.call
end

You can pass the return values from prepare to your method like so:

i,blk = prepare()
run(i, &blk)

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