Blocks, Procs, and Lambdas

Before starting to learn Classes, Objects and other essential features of Ruby, I would like to talk about closures. The closure is a variable that stores some executable code inside.

Everything you do inside of the closure will exist only inside of this it - nothing will be changed outside. Sometimes we want closure to return some values, or to make some calculations with already existed variables. In this case, we can always pass some data to a closure, or to make it return some value.

There are three types of closures in Ruby - Blocks, Procs (Procedures), and Lambdas.

Procs and Lambdas

Proc and Lambdas are both Proc objects (we could say Lambdas are based on Procs), but they have some small differences that may be hard to understand for a beginner. First of all, Lambdas treat "return" keyword differently - it will return from the code (scope) OUTSIDE of the lambda. Proc will return from its own scope.

Also, Lambda will throw and error if a user passes less or more values to it, when Proc will not.

These differences may not be that clear for beginners, so let's just use Lambdas for now.

To create a proc or lambda, you would need to use next syntax:

        l = lambda { 1 + 1}
        p = Proc.new { 1 + 1 }
      
There is a convention, where developers decided to use {} brackets for a single line function, and "do ... end" for a multiline:
        mp = Proc.new do
          x = 1
          y = 2
          x + y
        end
      

Lambdas and Procedures are good examples of a reusable code. If program has lots of repeatable code, you can write it once, and use it everywhere - it will save your time, will help you with bug fixing, etc. Let's imagine our app calculates average value of 3 numbers lots of times. Your code could look like this:

        avg1 = (1 + 2 + 3) / 3.0
        avg2 = (3 + 4 + 7) / 3.0
        avg3 = (120 + 4 + 0) / 3.0
      

All three lines of code make the same thing; it just uses different values. We could use Lambdas or Procs to simplify it a little bit.

As mentioned at the beginning of a page, we can pass some values to lambdas and procs, and they can also return some values. To describe values passed to the closure in Ruby, we use syntax that you can see below. (notice what is going on after "do" keyword - we tell Ruby that user is going to pass three different variables to the closure and that they will be named as in the list):

        calc_sum = lambda do |number1, number2, number3|
          number1 + number2 + number3
        end
      

In Ruby, the result of the last expression will be "returned". Our previous example will "return" to us a sum of all passed numbers when Lambda is executed. To run a closure (Block, Proc, Lambda), you need to use "call" method in it, and specify variables or values inside of brackets:

        sum = calc_sum.call(1, 2, 3)
      

Let's go back to our example with average values of three numbers. How would we implement a Lambda for this?

        calc_avg = lambda do |n1=0, n2=0, n3=0|
          return (n1 + n2 + n3) / 3.0
        end

You may see some new things in here. First, we have added some =0 now. You can read it as "if the value is not set, use 0 instead". You can also see a "return" keyword. If you remember, in a first chapter we talked that Ruby is very flexible, and things like spaces or brackets can sometimes be omitted. "return" keyword can be also omitted - Ruby will automatically return the result of the last expression for you.

Now we can replace our old code with our new lambda:

Blocks

Blocks are different from procs and lambdas. Block is basically just a part of a syntax. You can only pass blocks as some kind of a variable, but you can't create it just like a Proc or Lambda. Let's use Array's find method as an example. This method helps you to find a value inside of an array:

        arr = [ { value: 1, name: "One" }, { value: 2, name: "Two"} ]
        val = arr.find { |o| o[:value] > 1 }
        # => { :value=>2, :name=>"Two" }
      

In this example, find method will return the second object because it is the only hash where :value is higher than 1. Everything that you can see after the word "find" is a block. Ruby gives us one variable that we decided to name "o", that represents each value inside of an array. Then we have our own code written:

        o[:value] > 1
      
We could write whatever we want there, but we were looking for array's element that has :value higher that 1. Ruby has iterated over all array values, tried our expression on all of them, and it returned us first value for which our block has returned true value:

Blocks are everywhere in Ruby. If we want to iterate over all array elements, we could use each method, and pass it a block, where we do something with a given array element:

        # create an array
        arr = [ 1, 2, 3, 4, 5 ]

        # iterate over all array elements, and multiply each element by -1:
        arr.each { |el| p(el * -1) }
      
In this example, "p" keyword is a command that prints something to console. In our case, it prints the result of el * -1 expression. Try to run the code without it:
        arr.each { |el| el * -1 }
      

Hashes also use blocks a lot:

        h = { a: 1, b: 2, c: 3 }
        h.delete_if { |key, value| key == :c }
      

Blocks are everywhere. You will use them with Strings, Arrays, Hashes, will learn how to use them with your own methods, etc. But for now, let's continue to learn Classes.

Book Index | Next