Posted on Feb 21, 2018 [ 3 min read ]

Understanding Blocks, Procs & Lambdas

In this post you will learn everything you need to know about Ruby procs, blocks & lambdas.

Understanding Blocks

Blocks are very prevalent in Ruby, you can think of them as little anonymous functions that can be passed into methods.

Blocks are enclosed in a do / end statement or between brackets {}, and they can have multiple arguments. The argument names are defined between two pipe | characters.

If you have used each before, then you have used blocks! Here is an example:

# single line blocks
[1, 2, 3].each { |num| puts num }

#multi-line blocks
[1, 2, 3].each do |num|
  puts num
end

So how does a method work with a block, and how can it know if a block is available? Well, to answer the first question, you need to use the yield keyword. When you use yield, the code inside the block will be executed.

def print_once
  yield
end

print_once { puts "Block is being run" }

# Output
# Block is being run

Yield can be used multiple times, which will result in the block being executed as many times as you call yield.

def print_twice
  yield
  yield
end

print_twice { puts "Hello" }

# Output
# Hello
# Hello

It is also possible to use yield with any number of arguments. These arguments can then be used by the block.

def one_two_three
  yield 1
  yield 2
  yield 3
end

one_two_three { |number| puts number * 10 }

# Output
# 10, 20, 30

Blocks can also be explicit instead of implicit. What this means is that you can name the block and pass it around if you need to. Here is an example:

def explicit_block(&block)
  block.call # Same as yield
end

explicit_block { puts "Explicit block called" }

# Output
# Explicit block called

If you try to yield without a block you will get a no block given (yield) error. You can check if a block has been passed in with the block_given? method.

def do_something_with_block
  return "No block given" unless block_given?
  yield
end

Blocks are used as arguments to other methods (like #each), just like the normal arguments that you see between the parentheses... they just happen to always be listed last and on their own because they tend to take up multiple lines. Don't think of them as anything too special. The #each method isn't special either, it's just built to accept a block as an argument.

How does #each take a block then? Through the magic of the yield statement, which basically says "run the block right here". When you write your own methods, you don't even need to specially declare that you'd like to accept a block. It will just be there waiting for you when you call yield inside your method.

yield can pass parameters to your block as well. See this made-up version of the #each method to get an idea of what's happening under the hood. We'll put this method into the Array class so you can call it directly on an array (like [1,2,3].my_each) instead of having to take the array as an argument like my_each([1,2,3]):

class Array 
  def my_each
    i = 0
    while i < self.size
        yield(self[i])  
        i+=1      
    end
    self
  end
end

As you can see, we iterate over the array that our #my_each method was called on (which can be grabbed using self). Then we call the block that got passed to #my_each and pipe in whatever member of the original array we are currently on. Last, we just return the original array because that's what #each does. We would run it just the same way as #each:

> [1,2,3].my_each { |num| print "#{num}!" }
1! 2! 3! => [1,2,3]

Which operates in that case just like all these lines:

class Array
  def my_each
    i = 0
    while i < self.size
        print "#{self[i]}!"   # Our block got "subbed in" here
        i+=1
    end
    self
  end
end

So one reason blocks are great is because you can write a sort of generic method like #each which wraps your block in code that says what to do with it. Another use case is when creating methods where you want to optionally be able to override how they "work" internally by supplying your own block -- #sort lets you supply your own block to determine how to actually order the items of the array if you want to!

What if you want to pass TWO blocks to your function? What if you want to save your block to a variable so you can use it again later? That's a job for Procs, aka Procedures! Actually, a block is a Proc (which is the class name for a block) and they rhyme just to confuse you. The block is sort of like a stripped-down and temporary version of a Proc that Ruby included just to make it really easy to use things like those #each iterators.

A Proc is just a block that you save to a variable, thereby giving it a bit more permanence:

> my_proc = Proc.new { |arg1| print "#{arg1}! " }

Use that block of code (now called a Proc) as an input to a function by prepending it with an apersand &:

> [1,2,3].each(&my_proc)
1! 2! 3! =>[1,2,3]

It's the same as passing the block like you did before!

When you create your own function to accept procs, the guts need to change a little bit because you'll need to use #call instead of yield inside (because which proc would yield run if you had more than one?). #call literally just runs the Proc that is called on. You can give it arguments as well to pass on to the Proc:

> my_proc.call("howdy ")  # edit: note that this is the same `my_proc` as above
howdy => nil

Most of the time, using a block is more than sufficient, especially in your early projects. Once you start seeing the need for using a Proc (like passing multiple arguments or saving it for later as a callback), you'll have Procs there waiting for you.

Blocks and Procs are both a type of "closure". A closure is basically a formal, computer-science-y way of saying "a chunk of code that you can pass around but which hangs onto the variables that you gave it when you first called it". It's the blanket term used to refer to blocks and Procs and...

There are two other similar closures to be aware of but about which you certainly don't need to be an expert because they're used in less typical applications. The first of these is a lambda. If Procs are sort of a more-fleshed-out version of blocks, then lambdas are sort of a more-fleshed-out version of Procs. They are one step closer to being actual methods themselves, but still technically count as anonymous functions. If you're coming from Javascript, anonymous functions shouldn't be anything new to you.

Just to focus on the differences between lambdas and Procs, a lambda acts more like a real method. What does that mean?

  • A lambda gives you more flexibility with what it returns (like if you want to return multiple values at once) because you can safely use the explicit return statement inside of one. With lambdas, return will only return from the lambda itself and not the enclosing method, which is what happens if you use return inside a block or Proc.
  • Lambdas are also much stricter than Procs about you passing them the correct number of arguments (you'll get an error if you pass the wrong number).

Here's a simple example to show you the syntax of a lambda (btw, there's nothing special to lambdas about placing the #call after the end, if you hadn't seen that done before, it's just like method chaining):

> lambda do |word| 
>   puts word
>   return word            # you can do this in lambdas not Procs
> end.call("howdy ")
howdy => "howdy "        # not nil because we gave it a return

The second additional closure is called a Method because, well, it's the closest of the four (blocks, Procs, lambdas, and Methods) to an actual method. Which it is. "Method"'s (capitalized because they're actually a class of their own) are really just a convenient way to pass a normal method to another normal method by wrapping the symbol of its name in the word method() So what? To use the same example as we have been so far:

Edit: Note that #my_each has been modified for this example to now take an argument, which the standard #each does not. We're using #my_each below.

class Array
  def my_each(some_method)
    i = 0
    while i < self.size
      some_method.call(self[i])
      i+=1
    end
  end
  self
end

def print_stuff(word)
  print "#{word}! "
end

> [1,2,3].my_each(method(:print_stuff))    # symbolize the name!
1! 2! 3! => nil

To summarize:

  • Blocks are unnamed little code chunks you can drop into other methods. Used all the time.
  • Procs are identical to blocks but you can store them in variables, which lets you pass them into functions as explicit arguments and save them for later. Used explicitly sometimes.
  • Lambdas are really full methods that just haven't been named. Used rarely.
  • Methods are a way of taking actual named methods and passing them around as arguments to or returns from other methods in your code. Used rarely.
  • Closure is just the umbrella term for all four of those things, which all somehow involve passing around chunks of code.
X
- +
B
A - Z
Copyleft 2024 Gritwerkz.

Twitter Email

Back to Top