Overview
By the end of this chapter, you will be able to use blocks to improve code reusability; implement yield with blocks to maintain control over program flows; implement procs and lambdas with blocks and compare procs and lambdas.
This chapter aims to give you a general overview of metaprogramming.
In the previous chapters, we learned how to debug Ruby programs using the logger class. In this chapter, we will learn about some of the advanced topics of the Ruby programming language, such as blocks, procs, and lambdas. By now, you must have realized that it's really easy to start writing Ruby code, create sample apps, and get instant gratification; however, every new Ruby developer (and sometimes the experienced ones too) will have a question buzzing in their mind about how it actually works.
We can safely call it the Ruby magic, and that's what we are planning to crack in this chapter. We are going to learn about advanced programming techniques that are present in other languages, but this time the Ruby-specific implementation is our focus. We will tear down the curtain from the syntactic sugar and take a look under the hood to reveal what metaprogramming is. Additionally, we will lay a strong foundation that will allow you to reuse these techniques and enable you to utilize the libraries provided by Ruby. Later, when we learn about Ruby on Rails in the upcoming chapters, it will be easier to comprehend various features and how they operate behind the scenes.
You might be wondering what metaprogramming actually is. Well, it is utilized by most programming languages. Technically, it's a piece of code that writes code by itself dynamically. With metaprogramming, you can create methods and classes at runtime. It allows you to reopen or even modify classes, as well as add methods to classes on the fly. It helps to maintain the Don't Repeat Yourself (DRY) principle in programming; however, it is a hard concept to grasp and get right.
Let's imagine the scenario of a chef in a restaurant. This chef needs to know many recipes and be able to put together food for their customers. Each food item requires different steps for preparation. There are some steps that need to be done before, during, and after the food is prepared. In order to create a simulation for these steps to be performed by an automated system, you might consider using the metaprogramming feature provided by the Ruby language.
Metaprogramming makes use of multiple elements available within the Ruby language. Let's take a look at the first building block, which is called blocks in Ruby.
Blocks are like anonymous functions – lambdas – that can be passed into a method. You may also refer to the use of a block as passing a method into a method. The slight difference in blocks and methods is that they do not belong to an object. In other languages, such as C#, Java, or Python, this concept is called closures. Blocks are just the Ruby way of implementing closures. Blocks are typically used when we would like to encapsulate functionality that is made up of a sequence of statements. For example, we could encapsulate an "Introduction Letter" email when new people arrive at a company, where we just have to pass a template to a block and it automatically generates and sends the email. The only variable here is the detailed information of the new colleague in the template.
There are two ways to write blocks: they can be either enclosed in do/end or within curly brackets ({}). Let's examine a simple example showing how to write them.
Consider the following example:
(1..5).each do
puts "I am inside the block"
end
The output would be as follows:
Consider the following example:
5.times {puts "I am inside the block"}
The output would be as follows:
Besides this, we can also use the yield keyword to write blocks, which we will discuss shortly.
In Ruby, many built-in class methods allow you to pass blocks. This helps in enhancing the feature set provided by the built-in methods.
The preceding examples are pretty basic for understanding block syntax. Let's try and understand this with another example of using a block as an iterator variable in order to print something:
def star_tree number
1.upto(number).each do |i|
puts "*" * i
end
end
star_tree 30
Fire up your Terminal and run this Ruby file:
$ ruby block-iterator.rb
The output would be as follows:
Let's understand what we have done here. We are calling a star_tree method and passing a value of 30 to it, which is then used by the block as an iterator variable number, which helps to create a behavior inside each method. This was a very simple implementation of blocks that is used quite often in real-world programming. Let's learn about yield and how to use it with blocks to make really powerful programs.
Blocks can be passed to methods just like any other parameter. We have been using blocks all along without realizing it. Take a look at the preceding code with .each. What is running inside a .each method is a block, and we define what needs to be implemented in that .each method.
The ability of blocks to be passed as parameters makes them a great candidate for applying abstraction, which is a very important concept of object-oriented programming (OOP).
The yield construct in Ruby is essentially a way to call a block. Wherever you use this keyword, the block code will be executed there. If there is code before the yield construct, it will be executed first and then the rest of the code is executed. Other programming languages, such as Python and JavaScript, also utilize the yield construct. You can yield an empty block; however, nothing will happen as there is nothing to execute.
Let's understand this with an example:
def my_method(&block)
puts "Part one of my_method code"
yield
puts "Part two of my_method code"
end
my_method {puts "Yield code from the Block"}
The &block argument expects a block that will be executed when the control reaches the yield line.
The output would be like the following:
Let's understand this program. We pass a block of code to the my_method function which executes the block when the control reaches the yield statement. Now, imagine the possibility of using the yield keyword to pass a block of code with some operations. With yield, you can do all the behind-the-scenes magic we generally associate Ruby methods with.
If we try printing yield twice, or maybe after the second puts statement in my_method, the yield construct will call each line of the block that you pass to the program. If you pass two statements, each statement will be executed as many times as there are yield statements in the code.
Consider the following example:
def my_method(&block)
puts " Before first yield"
yield
puts " After first yield"
yield
end
my_method {puts "What happens with two yields?"}
The output of the first yield call with one statement will be as follows:
Now let's take a look at the output for multiple statements:
my_method do
puts "First statement"
puts "Second statement"
end
The output should be as follows:
In this exercise, we will be creating a simple calculator that performs basic arithmetic operations such as addition, subtraction, multiplication, and division. We will be using Ruby blocks and yield to create reusable code. The following steps will help you complete the exercise:
def calculator(a, b)
yield(a, b)
end
#Addition
puts calculator(8, 2) { |a, b| a + b }
#Multiplication
puts calculator(8, 2) { |a, b| a * b }
#Subtraction
puts calculator(8, 2) { |a, b| a - b }
#Division
puts calculator(8, 2) { |a, b| a / b }
Save the file.
ruby block-yield.rb
The output should be as follows:
The beauty of this program is that we can add n number of operations using the combination of a block and yield in our code.
The block_given? method is something we use in tandem with blocks and the yield keyword. As the name suggests, it checks whether or not a block for triggering a method or operation has been provided. Let's take a look at its syntax:
def my_method(&block)
puts "Part one of my_method code"
yield if block_given?
puts "Part two of my_method code"
end
my_method {puts "Yield code from the Block if block is given"}
my_method {puts "Yield code from the &block if it was passed as argument"}
If you execute this code from the Terminal, you will get the following output:
If we don't pass anything to the &block method, we get the following output:
my_method
The output would be as follows:
In this example, we have passed a block of code to my_method, which is a print statement, and have also attached block_given? to the yield keyword. Note that if you remove the block of code and call the method, it will still work and print only two other statements from the method, as seen in the previous example.
If we remove the block of code and also block_given? from my_method, it will throw us an error as shown in the following figure. This is due to the fact that &block is still expecting a block as an argument:
In this exercise, we will create a method that accepts blocks. If a block is passed, it should call the logger function, which is defined in another method. If no block is passed, it will simply print the statements. We call the method twice: once with a block, and then once without a block. This will visualize the updates in the flight status.
The following steps will help you complete the exercise:
def basic_logger
sleep 3
puts "****"
puts "Changing flight status…"
sleep 3
puts "Flight status was changed!"
sleep 3
puts "****"
end
def my_method(&block)
puts "Flight Status changer starting"
yield basic_logger if block_given?
puts "Flight Status changer ended"
puts "****"
end
my_method
my_method {puts " Changed Flight status to Departure "}
Here, we have called our my_method method, once without a block of code and once with it. In both cases, we get the appropriate result.
The output, when we call the method without any argument, would be as follows:
The output, when we call the method with an argument, would be as follows:
If we remove block_given? from my_method, the basic_logger method will be called to check whether we passed a block or not.
The proc object is an object of the Ruby class and is used to store a process that can be used later on. proc objects are blocks of code that can be set to local variables. Once set, this code can be called in different contexts and can access those variables.
Let's take a simple example:
t = Proc.new { |x,y| puts "A very simple proc example" }
t.call
The output would be as follows:
Another key feature of proc is that it can accept arguments. Ruby supports the functional programming paradigm and the proc object is one example of this. There are two distinct features that separate blocks from proc objects. Firstly, and most importantly, proc objects are objects, while blocks are not. Secondly, you can have only one block passed as an argument to functions, but proc objects do not have such restrictions.
In other languages, such as Python and JavaScript, proc objects are referred to as closures; many languages have their own paradigm for the function of proc objects in Ruby. Let's take a look at the syntax of proc:
def proc_method
Proc.new
end
simple_proc = proc_method { "Hello World" }
simple_proc.call
Here, we first initiate a proc object with Proc.new inside our proc method. This is then called by using .call and passing a block of code to it:
p = Proc.new { puts "Hello World" }
p.call
The output would be as follows:
Until the .call method is invoked, the proc object lies dormant; nothing happens.
Note
Remember that you must always pass a block of code to procs, as only then will a proc object be instantiated.
In the following exercise, we will discover how to implement proc objects in a program.
In this exercise, we will be writing a program to find the sum of a range of numbers. For example, if the start and end of the range are 1 and 6, then the final sum total will be 21.
The following steps will help you with the solution:
sum_of_range = Proc.new do |first, last|
result = 0
(first..last).each do |i|
result += i
end
result
end
puts sum_of_range.call(1,6)
ruby proc-sum-range.rb
If you have ever wondered how the inject method works, then this is a very simple implementation of the same thing, and we achieved it with a few lines of code.
The difference between methods and proc, even if they look quite similar, is that while both methods and proc are blocks of code, methods are bound to objects while proc is bound to the variable it is assigned to. Methods represent the object-oriented part of Ruby, while proc represents the functional programming side of Ruby.
In this exercise, we will create a proc object that takes as input a list of prices and returns the new list with a 25% profit.
The following steps will help you to complete the exercise:
original_price = [100,200,300,400,999]
puts "The original price for the stocks: #{original_price}"
price_profit = Proc.new do |price_list|
result = Array.new
price_list.each do |i|
result << i * 1.25
end
result
end
new_price_with_profit = price_profit.call(original_price)
puts "The new price for the stocks #{new_price_with_profit}"
The output should be as follows:
Just like proc objects, lambdas are also examples of closure in Ruby. Lambdas, just like proc, are simply a function with a name. Additionally, just like proc, they are objects too. We will be looking at the specific differences between proc and lambdas later on in the chapter.
Let's now look at the following syntax to learn more about lambdas:
hello = lambda {puts "Hello World"}
hello.call
That's really simple. We use the lambda keyword to pass a block of code. To call a lambda function, we simply use .call, in our case, on the variable assigned to it.
There is another very cool and commonly used syntax of lambdas called stabby lambda, which is represented as ->; it was first introduced in version 1.9.
The preceding code snippet will change to the following when stabby lambda is used:
hello = -> {puts "Hello World"}
hello.call
Let's implement lambdas in an exercise. Since proc and lambdas are quite close in terms of implementation, to understand them well, let's implement the same problem using lambdas instead of proc.
In this exercise, we will write a program to sum a range of numbers using lambdas. The following steps will help you to complete the exercise:
sum_of_range = ->(first, last) do
result = 0
(first..last).each do |i|
result += i
end
result
end
puts sum_of_range[1,6]
We have successfully used lambda to create a sum function.
Note
We can use the square brackets with proc too, that is, instead of using .call when passing arguments to both proc and lambda.
The key difference between procs and lambdas is the way the return statement works for both of them. While proc ignores the container method, lambda does not override it.
A return statement in a block of code created using lambda behaves similarly to how a method uses return: that is, it simply exits the block handling the control back to the original method. However, in the case of a block of code created using proc.new, when the return statement is used, it returns from both the blocks of code and the method.
Let's understand this with an example:
def my_lambda_method
lambda { return "Statement from the block" }.call
return "Statement from the method"
end
puts my_lambda_method
def my_proc_method
Proc.new { return "Statement from the block" }.call
return "Statement from the method"
end
puts my_proc_method
The output would be as follows:
If you take a look at the first method of this file, my_lambda_method, you will see that it works in a very similar way to any Ruby method, returning the final statement of the method, which even has a lambda with a return statement in it.
In the my_proc_method method, you can see something different happening. The code is exited altogether after seeing the first return statement from proc.
This is a very subtle difference between the two methods, but the difference is usually encountered when you are stuck in a bug. Therefore, we need to understand the internal workings of procs and lambdas and how they react to a return statement.
This section will round up metaprogramming with a live example as to how procs and lambdas work in real life. We will define three distinct parts of the operation. The main part of the magic happens in Chef.rb, the components are defined in Recipes.rb, and we will also have Order.rb.
Let's define Chef.rb. We will have a list of recipes and some filters. We will have cook, the before and after functions to put together the food, and the run function, which will be an abstraction that is used to put together the parts:
Chef.rb
1 module Chef
2 def self.included(klass)
3 puts "The Chef is ready for service!"
4 end
5 def recipes
6 @recipes ||= {}
7 end
8 def before_filter
9 @before_filter ||= {}
10 end
11 def after_filter
12 @after_filter ||= {}
13 end
The entire preceding code should be saved as Chef.rb in a directory. This is an abstraction, which is what metaprogramming is all about; we don't know what will be ordered or how to cook the orders. We just want a chef who knows how to fulfill customer requirements.
Now it is time to create Recipes.rb; we will define what happens before, after, and during a chicken soup order:
require './Chef.rb'
cook "chickensoup" do
puts "Adding spices"
puts "Adding water if necessary"
puts "Waiting for the food to be ready"
end
before "chickensoup" do
puts "Preparing to cook chicken soup!"
puts "Preparing bowls, slicing and dicing."
end
after "chickensoup" do
puts "Cleaning dishes"
puts "Serving customer"
end
puts "End of chickensoup!"
This file can be further extended with more recipes and allows you to customize the orders. This also allows us to detach the functionality of the chef and the orders from the recipes. This makes for a good modular application.
Finally, we can define our Orders.rb file, which will be an interface to call the chef to fulfill the orders based on the recipes:
require './Recipes.rb'
run('chickensoup')
This is it. Save all three files in the same folder and you should be able to invoke the ruby Orders.rb command and see the following output:
Let's now solve an activity, where we will be implementing every topic we have learned about in this chapter.
In this activity, we will be creating an invoice generator program that will take the customer name, ID, and the amount for two products, then generate an invoice.
The variable names to be used are cust_name, cust_id, product1 p1, and product2 p2. Our example will have three functions: invoice_generator, calc_discount, and details.
The following steps will help you with the solution:
The output would be as follows:
Note
The solution for the activity can be found on page 484.
In this chapter, we learned about the key concepts of metaprogramming. We learned about concepts such as blocks and how to use yield and block_given? to make our blocks of code powerful. We then learned about proc and lambda and the differences between proc and lambda, and we also discovered how a subtle difference can help avoid confusion and bugs.
In the next chapter, we will be learning more about metaprogramming in Ruby.