Programming ≈ Fun

What the Heck Are Closures in Ruby?

What exactly is closure? It’s a convenient way of passing variables that you might need inside your block. But how does the block know which ones you’ll need? Well it doesn’t so it copies every single one of them. Only condition is they have to come in scope before the beginning of the block. Thats what I’ll be calling “scope” from now on.

Every variable that is in “scope” is copied and “combined” with the block. That way you can access them inside the block. Otherwise it would be cumbersome to add all those arguments that you need.

You can visualize it as if the block is dragging along all the buddies that he just met. Rule of thumb is that you are sending as an argument only variables that are needed for a method using block, not the block itself.

Let’s clarify this with an example (I’ll use explicit blocks because I think implicit blocks are evil, especially when trying to figure them out):

#passing the description to the block
def change_customer_name(id, full_name)
  with_logging('change_customer_name') do
   customer = Customer.find(id);
   customer.full_name = full_name
  end
end

def change_customer_age(id, age)
  with_logging('change_customer_age') do
   customer = Customer.find(id);
   customer.age = age
  end
end

def with_logging( description, &the_block )
  begin
    @logger.info("Starting request #{description}")
    the_block.call # do the actual thing
    @logger.info("Completed request #{description}")
  rescue
    @logger.error("Request failed, it's all your fault!")
  end
end

As you can see we have id and the customer name and age available in the block by convention. That is closure. Only argument that we are sending is a description that is needed for the method containing block, not the block itself.

Things to keep in mind:

  • Block saves a copy of variables in “scope”, so if you change them, original values are not changed
  • Watch out for the size of variables in “scope”. If you save block for later use, remember that you are saving the whole closure along with it

Ruby Implicit Blocks Are Evil

Don’t get me wrong, I love Ruby blocks, even the implicit ones because blocks are one of the best things in Ruby. The reason I am writing this post is that it took way too long for me to really get them. I’ve identified two major hurdles:

  • I didn’t know why on earth would I use them
  • That ‘clever’ use of implicit block and yield really messed up my brain

First things first. Why to use them?

You can:

  • Iterate, even to eternal
  • Remove boilerplate code with Execute Around (Kent Beck-Smalltalk Best Practices)
  • Save for later use aka solving-callback-problem

You really owe it to your self to read Eloquent Ruby from Russ Olsen, as he does such a great job of explaining Ruby finer points.

I’ll try to clarify mu second point on example of execute around block where I’ll write implicit vs. explicit version

#Boilerplate code that we hate so much and want to get rid of
def fetch_some_data(id)
  begin
    @logger.info("Starting request")
    @data = Data.find(id) # do the actual thing
    @logger.info("Completed request")
  rescue
    @logger.error("Request failed, it's all your fault!")
  end
end

Implicit version:

def fetch_some_data(id)
  with_logging { @data = Data.find(id) }
end

def with_logging
  begin
    @logger.info("Starting request")
    yield # do the actual thing
    @logger.info("Completed request")
  rescue
    @logger.error("Request failed, it's all your fault!")
  end
end

Explicit version

def fetch_some_data(id)
  with_logging { @data = Data.find(id) }
end

def with_logging( &the_block )
  begin
    @logger.info("Starting request")
    the_block.call # do the actual thing
    @logger.info("Completed request")
  rescue
    @logger.error("Request failed, it's all your fault!")
  end
end

The explicit version clicks with my mind instantly. I am accepting block as a parameter (I’ll admit syntax is a bit C-ish,but…), and then when the time is right I am executing block.

Nothing magical about it, just another parameter that happens to be a block of code.

Example is a bit too simple, because things get really confusing when we include parameters that we send into block. I am not going to start about that, or even worse about closures as it is 2AM, and I still need to deploy this on my home stored Arch/nginx server…me from the past is telling you it was a success as you are reading this :)

Copyright © 2019 - Kresimir Bojcic (LinkedIn Profile)