Josh Thompson     about     archive     tags

Blocks and Closures in Ruby

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.

Further reading


Get occasional emails

If I've written any new posts, you'll get an email with summaries on Friday. If I've not, you'll not hear from me.