4

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.

Isaac Betesh
  • 2,935
  • 28
  • 37

2 Answers2

2

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.

Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
  • "Ampersand in method call is used to say “hey, convert this to proc and use as codeblock” to ruby" -- I want to understand why it matters *where* I put the ampersand. Why, for instance, can't `widget_and_block` return `[args, &block]` (instead of `[args, block]`)? Furthermore, block already *is* a Proc, so shouldn't I really need to tell the method "Accept this proc as a proc and not as a regular argument"? Is that really what the "&" is doing there? – Isaac Betesh May 08 '15 at 16:25
  • Yes, it is. All that stuff is around resolving `yyield keyword. – Aleksei Matiushkin May 08 '15 at 16:31
  • You answer is misleading then. If you edit it to explain the Ampersand more precisely (ideally with a link to some more detailed explanation), I will mark this answer correct. – Isaac Betesh May 11 '15 at 15:25
0

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)
levinalex
  • 5,889
  • 2
  • 34
  • 48