Blocks and Closures in Ruby
Article Table of Contents
Continuing on from yesterday’s post about method_missing
, I’m moving on to a part of Ruby’s language that has been a bit of a mystery for me for quite some time. I’m still working through Metaprogramming in Ruby.
It’s the concept of lambdas, procs, blocks, and more. I also hope to better understand closures, and perhaps even some aspects of functional programming, as I’m used to (so far) thinking in OOP approaches.
I expect my first pass to be rocky, but to also lay a foundation for better understanding later. I’m writing this out because this is how I learn.
yield #
First new term: yield
. (docs)
It is always related to passing blocks around (to methods, or other blocks, procs, etc). I think lots of what we do with blocks interacts with the yield
function, but it gets called behind the scenes. (Sorta how initialize
calls, among other things, new
)
you can call yield
from inside a method to see any block passed to it. You can also call block_given?
to see if there’s a block floating around anywhere.
def a_method
return yield if block_given?
'no block'
end
a_method
=> "no block"
a_method {"look at me, I'm a block"}
=> "look at me, I'm a block"
a_method do
"i'm inside a do..end block"
end
=> "i'm inside a do..end block"
Enough on yielding.
Blocks are closures #
So, what is a closure? Great question…
A block requires an environment to run inside of. An orphaned do..end
block doesn’t make sense.
The environment the block runs inside of has local variables, instance variables, a self
, etc. (I’m copying heavily from Metaprogramming Ruby 2 here.) These variables are sometimes referred to as bindings. A block that has bindings is ready to run.
A block, with it’s given bindings (which relate to its scope), is known as a closure.
I don’t yet know why.
It seems like it’s tricky for blocks to gain access to variables outside of their immediate context/scope.
To pass scopes around, you have to get past “Scope Gates”, which is anything ruby method that starts with class
, module
, or def
. You might think to yourself “that’s all of them.”
To sneak a binding through a scope gate, you’ll have to use a block.
Compare:
my_var = "success"
class MyClass
p my_var, " from myClass"
# can we access my_var from here?
def my_method
p my_var, " from my_method"
# ... or here?
end
end
# womp womp. run the code, and receive:
# blocks_practice.rb:15:in `<class:MyClass>': undefined local variable or method `my_var' for MyClass:Class (NameError)
(you cannot access my_var
inside of the class or method. Those methods start with the scope-gated phrase class
and def
)
my_var = "success"
MyClass = Class.new do
# we can access my_var from here!
puts "#{my_var} from myClass"
def my_method
# ... but not from here
puts "#{my_var} from my_method"
end
end
# MyClass.new.my_method
# NameError: undefined local variable or method `my_var' for #<MyClass:0x007f98c93f0980>
my_var = "success"
MyClass = Class.new do
# we can access my_var from here!
puts "#{my_var} from myClass"
define_method :my_method do
# ... but not from here
puts "#{my_var} from my_method"
end
end
# MyClass.new.my_method
# => "success from my_method"
I feel like there’s something important here, but I don’t yet know what it is. But I’m not worrying about it - this is my first trip through closures in Ruby, and I’m only half way through the chapter!
Per how I like to learn, I’m going to dig into some associated guides/tutorials for the concept of closures in Ruby. I know it’s tied to functional programming, and I think it’ll be useful to me to wrap my head around the idea.