In the first part, I tried to cover the basics of code blocks. Now, we’re going to talk about : Code blocks and scope
A code block is a closure
Oh no… not another definition of a closure? Yes, but I promise I will be quick. First, to understand what a closure is we have to understand what a scope is. A scope is a region of your code where the variables share the same life cycle. You can’t access the components that are located inside a given scope when you’re out of it. For example, when you write a method, every local variables you define are located in the same scope. The outside world can’t access them because these local variables are living in another execution context (scope) and they only exist for a short period of time. When the interpreter is done executing the method, its scope is destroyed by the garbage collector.
A closure is a collection of 1 or more lines of code that has it’s own scope attached to it and that is defined inside the body of a method. What makes a closure a closure is that it keeps its own scope when the outer method has finished executing. More than that, the closure will still have access to the scope above, that is, the scope of the method where the closure was defined (the outer method). With closures, scopes that normally would have been destroyed see their lives extended.
So :
- A closure has it’s own scope
- A closure is defined inside the body of a method (that’s another scope)
- A closure has the power to extend the normal life of these 2 scopes
- A code block is a closure
But to be a real closure, a code block must be stored in a Proc object. Why? Because if you pass a block to a method and that this method invokes it with yield immediately without storing it anywhere, everything will stay “in scope” from the beginning to the end. That’s quite a boring closure if you ask me (but it’s still the only thing we need most of the time). When I think of a closure, I don’t want to think of something volatile like that, I want to think of something persistent … something that will live as long as I want.
Here is a small example I wrote to clear this out :
class ScopeTest
def defining_the_block
outer_scope_var = "hello!"
@proc_object = Proc.new {puts outer_scope_var} #we store the closure for future use
end
def calling_the_block
@proc_object.call
end
def test_it_all
defining_the_block
# ... Thousands of lines of code here
# more lines of code
# Oh! God! I forgot about that block I have defined earlier! I wonder if it is
# still alive. (TODO : Stop writing dumbass comments in production code)
calling_the_block #output => hello!
end
end
As you can see, storing the block inside the @proc_object instance variable kept its scope from being wiped out by the garbage collector. It did also preserved the scope of the outer method defining_the_block, that’s why the local variable outer_scope_var was still alive and accessible by the closure at the very end of the listing. Without the existence of the closure, that variable would have been destroyed at the end of defining_the_block.
I’ll stop right there. In the first part I said I would talk about lambdas… I lied, sorry. I thought it was spelled lambada, that’s why I was very excited to write about this very hot dance from Brazil. Once I learned that it was spelled lambda, it just turned me off.
I got you… believe it or not this isn’t the real reason! The real reason I’m not writing about lambdas is because I think the article is long enough. Maybe in the next part, if there is one, I’ll write about them.