Over a million developers have joined DZone.

Ruby closures addendum - yield

·

This should probably have been part of one of my posts on closures or defining methods, but I'm just going to write this separately, because it's a very common mistake.

So, say that I want to have a class, and I want to send a block when creating the instance of this class, and then be able to invoke that block later, by calling a method. The idiomatic way of doing this would be to use the ampersand and save away the block in an instance variable. But maybe you don't want to do this for some reason. An alternative would be to create a new singleton method that yields to the block. A first implementation might look like this:

class DoSomething
def initialize
def self.call
yield
end
end
end

d = DoSomething.new do
puts "hello world"
end

d.call
d.call

But this code will not work. Why not? Because as I mentioned in my post
about defining methods, "def" will never create a closure. Why is this
important? Well, because the current block is actually part of the
closure. The yield keyword will use the current frames block, and if
the method is not defined as a closure, the block invoked by the yield
keyword will actually be the block sent to the "call" method. Since we
don't provide a block to "call", things will fail.

To fix this is quite simple. Use define_method instead:


 class DoSomething

def initialize
(class << self; self; end).send :define_method, :call do
yield
end
end
end

d = DoSomething.new do
puts "hello world"
end

d.call
d.call

As usual with define_method we need to open the singleton class, and use send. This will work, since the block sent to define_method is a real closure, that closes over the block sent to initialize.

Topics:

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}