Can I pass a block which itself expect a block to instance_exec in ruby?
Asked Answered
D

2

11

I expect the code

foo=proc{puts "foo"}

instance_exec(1,2,3,&foo) do |*args , &block|
  puts *args
  block.call
  puts "bar"
end

to output

1
2
3
foo
bar

But got the error

both block arg and actual block given

Can I pass a block which itself expect a block to instance_exec in ruby?

Draconic answered 12/3, 2013 at 9:5 Comment(0)
S
6

&foo tries to pass foo as a block to instance_exec, and you are already passing an explicit block. Omitting ampersand sends foo just like any other argument (except that it is a Proc instance). So, try this instead:

instance_exec(1,2,3,foo) do |*args, block|
  puts *args
  block.call
  puts "bar"
end

This also means that you can do something like:

bar = proc{ |*args, block|
  puts *args
  block.call
  puts "bar"
}

instance_exec(1,2,3,foo,&bar)

And get the same result.

More info at Difference between block and &block in Ruby

Schurman answered 12/3, 2013 at 9:40 Comment(1)
Unfortunately this won't work for my case. I'm trying to transparently wrap an existing proc in a way that allows me to track its execution time. Since I have no control over the signature of the proc I'm wrapping, I can't use this method. In general though this seems like a good work-around.Prana
E
3

I'm about 3 years late to this party, but I thought I'd share an approach that let's you treat the inner block more like a real block, rather than just a plain old argument.

The best way I know of to go about this is to create an object to act as a binding context and define the outer block as a method. So if I rewrite the original example as follows without the instance_exec call...

inner_proc = proc { puts "inner" }
outer_proc = proc { |*args, &inner_block|
  puts *args
  inner_block.call
  puts "bar"
}

We can define outer_proc as a method on an object

scope_object = Object.new
scope_object.define_singleton_method :bound_proc, &outer_proc

Now you can call scope_object.bound_proc instead of the instance_exec call above.

scope_object.bound_proc 1, 2, 3, &inner_proc

You'll get:

1
2
3
inner
bar

Unfortunately, you'll get a LocalJumpError if you try to yield inside of outer_proc, rather than the inner_block.call, I'm not entirely sure why. If someone has that answer then I'd be interested.

Egmont answered 17/11, 2016 at 22:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.