Isaac Betesh
Isaac Betesh

Reputation: 3000

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.

Upvotes: 4

Views: 103

Answers (2)

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121000

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.

Upvotes: 2

levinalex
levinalex

Reputation: 5949

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)

Upvotes: 0

Related Questions