Back-end Engineering Articles

I write and talk about backend stuff like Ruby, Ruby On Rails, Databases, Testing, Architecture / Infrastructure / System Design, Cloud, DevOps, Backgroud Jobs, and more...

Twitter:
@daniel_moralesp

2019-06-14

Ruby Functions

So far, we've seen how Ruby works and the type of data we can work with inside Ruby. So, we can say now that we know Ruby in more detail than in the beginning. We need to see a tool that can help us organize our code and do it more dynamically. The name of this is tool is Functions.

Functions

A function is a collection of several lines of code. We call all those lines at once without repeating the code by calling a function. A function is a tool that you can repeatedly use to produce a consistent result from different inputs. If you see the previous definition of functions, it is easy to figure out why it is so popular among developers, programming languages, and its utility.

Basically, we're all the time creating functions that contain our logic and at the same time calling functions from other sources. 

To write a function in Ruby, we need a header and a code block. The title starts with the word "def" and the function name. If the function receives a parameter, you can put parentheses after the function's name; if you don't receive parameters, it is unnecessary to have parenthesis (we'll see what's a parameter later on this same blog post). Then, the body of the function runs some kind of operation. The syntax looks something like the following:

Note: we'll use the text editor and execute the code via console. Please refer to this post to know how we do this.

def hello
  puts "Hello World!"
end



But what will happen if we execute this code now? Will it prints the string "Hello World!"? Let's see

It doesn't print out anything, but why?

This is because the function acts as a code container, but you need to call that container to be executed. So if you don't call the code container, you won't see any result.

Calling Functions

We can say that Functions are divided into 2 acts:

  • * The act of creation of the function
  • * The act of calling a function

In the first act, we create all the logic inside the functions, and then we need to call the function to be executed by the machine. If we don't call the function, the code inside that function won't run in any manner. We call a function straightforward; you just need to type the name you give to the function. 

# Step 1. The act of creation of the function
def hello
  puts "Hello World!"
end

#Step 2. The act of calling a function
hello



Now, if we go to the terminal and execute the file again, we'll see the text printed out.



So far, we've just a "puts" statement inside the function body, but later we'll start filling out the functions with a lot of code that contains the logic of our program.

Parameters/arguments

The following construction block inside the functions is the parameters. A parameter is basically a variable that we pass through the function, and we do something according to the value of that parameter. That means that functions help us to change something dynamically inside the function. Let's see an example.

# Step 1. The act of creation of the function
def hello(name)
  puts "Hello #{name}!"
end

#Step 2. The act of calling a function
hello("Daniel")


Let me explain the lines of code here.

  • * In the first line, we've declared the function and the function name, but immediately after the function name, we have a parameter
  • * The parameter is declared inside the parenthesis, and we can give any name we want (unless you use a reserved word). We must follow the same convection names that we saw in variable declarations.
  • * Now, we're using the parameter inside the body of the function, and we interpolated it with the string
  • * Finally, we call the function passing the required parameter. If you don't give any value to the parameter, you'll get an error. Now we have a more dynamically code

Let's try different behaviors.

def hello(name)
  puts "Hello #{name}!"
end

# Call the function without the parameter
hello




As you can see in the previous example, we got an error if we declare a function with a parameter and don't pass anyone on the call in the second step. The Ruby error is clear: "wrong number of arguments (given 0, expected 1)". Now let's see another situation.

def hello(name)
  # It's not mandatory to use the parameter
  puts "Hello World!"
end

hello("Daniel")



What do you see differently in the last example? The function is called correctly, with the parameter. However, the parameter is not used inside the function. 

The question is, does this make any sense? The answer is probably not. We do not have any errors, but when we declare a parameter usually is because we'll be using it inside the function to do something dynamically. So if we're not going to use that parameter, it is better not to declare it. 

Let's see another situation.

# Two parameters declared
def hello(first_name, last_name)
  puts "Hello #{first_name} #{last_name}!"
end

# Two parameters called
hello("Daniel", "Morales")



Now we have declared two parameters instead of just one. We've used both inside the function because we interpolated it with the string. And finally, we call the function with the two parameters. 

How many parameters can we declare (and then call) in Ruby? Any number of parameters you want. However, it is a good practice to keep that number low. One of the principles about functions is the Single Responsibility principle which says that each function should have just one responsibility. 

If your functions are receiving a ton of parameters (like 6, 8, 10, or more), probably you'll do a lot of things inside just one function, 

It is perhaps better to separate concerns and create 2 or more functions derived from this bigger one and then connect them and make calls between them accordingly.

In similar situations as the one mentioned above, if we've declared two parameters, we call just one. We'll get an error again.

# Two parameters declared
def hello(first_name, last_name)
 puts "Hello #{first_name} #{last_name}!"
end
 
# One parameter called
hello("Daniel")



And finally, what will happen if we change the order of the parameters when we call it? 

# Two parameters declared in order first_name, last_name
def hello(first_name, last_name)
 puts "Hello #{first_name} #{last_name}!"
end
 
# Two parameters declared in order last_name, first_name
hello("Morales", "Daniel")
 



As you can see, the print now is different from what we really want to do. So we also have to take care of the order of the parameters when calling them because we'll have wrong behaviors inside the code execution. So let's see this more in detail.

Keyword arguments/parameters

To avoid the last error (actually, it wasn't an error, but is not printing what we want), where we call the function with parameters in a different order, we can use keyword arguments. Arguments are synonymous with parameters, so don't confuse this other fancy term. So let's see an example.

# Two parameters declared in order first_name, last_name
def hello(first_name:, last_name:)
 puts "Hello #{first_name} #{last_name}!"
end
 
# Two parameters called in order last_name, first_name
hello(last_name: "Morales", first_name:"Daniel")



Now let's explain each line of code.

  • * In the first line, we have the function's name declared, and we change the names of the parameters adding a colon ":" at the end of each argument name. Why is that? Is the way Ruby can identify the name of the parameters when we call it 
  • * In the last line, we call the function but with a bit of change: we call the name given to each parameter followed by a colon ":" and then given the value for that parameter. 

In the beginning, all of this can seem pretty awkward, but as you can see, when we print the result, it doesn't matter the order of the parameters in the calling; they'll be printed out in the correct order when we use keyword arguments. 

Default arguments/parameters

We can have the other situation if we want a default parameter because it is standard, but it sometimes changes. So here, we can use the default parameters, and we can use the following syntax. 

# Two parameters declared and one of them is given by default
def hello(first_name, last_name="Morales")
 puts "Hello #{first_name} #{last_name}!"
end
 
# One parameter called, because it will take the default one
hello("Daniel")




Now, what happened here?
  • In the first line of code, we declared 2 parameters, but the second one is followed by an "equal" sign and a value. This value will be given to the function if we don't pass anything from the function call.

  • When we call the function we've passed just one parameter, and we don't get any error. This is because the function will take for us the default value assigned to the parameter last_name 

Return statement

When we start to have more code and logic inside the block of our functions, we have to consider the result we want to return from our function. This means that functions typically are created with a result in mind. For instance

  • * Return a string with the result of an operation
  • * Return a Boolean that gives us the answer to a question
  • * Return an Integer after doing some math internally

As you can see, we're talking about returning different types of values. So far, we've just returned interpolated strings to greet the user. Now let's change our code to demonstrate the benefit of the "return" keyword. 

 
def hello(first_name, last_name)
 "Hello #{first_name} #{last_name}!"
 "Hola #{first_name} #{last_name}!"
 "Ciao #{first_name} #{last_name}!"
end
 
# Call assigned to a variable
my_variable = hello("Daniel", "Morales")
puts my_variable


Let's talk about this code and try to figure out the output. 

  • * First line keeps the same as before and it received 2 parameters
  • * We added 3 lines of code without the "puts" statement, and we interpolated the parameters with a string that greet in 3 different languages
  • * Then, we call the function and assign that function to a variable. This is new for us. Can we give the call of a function to a variable? Yes, we can. And we do this because later, we can probably need the variable to use it in different ways. 
  • * Finally, we have the "puts" statement

The question is, what do you think this code will print?


Well, it printed out just the last line of code inside the function's body. We can call this "implicit return" because we don't use the keyword "return" to reach the last line. Functions in Ruby will always return the last line of code in the function if any "return" is declared. Let's see this code.

def hello(first_name, last_name)
 "Hello #{first_name} #{last_name}!"
 return "Hola #{first_name} #{last_name}!"
 "Ciao #{first_name} #{last_name}!"
end
 
# Call assigned to a variable
my_variable = hello("Daniel", "Morales")
puts my_variable



The return was called in the second line of the function, and that's the value we printed out in this way. Again, we used the keyword "return" When the interpreter sees a return inside the function, all other code below won't be executed. 

This is quite interesting because we'll have more autonomy to choose what values return and what values don't. This is what we call "explicit return" because we're telling the program what to return and what don't

Functions vs. Methods

Now that we know a bit more about Ruby functions is time to see what's a method in Ruby. Actually, it is the same thing, but with a slight difference. 

  • * We've not seen OOP (Object-oriented programming) yet, but a method is a function wrapped inside a class. A class is how we declare objects in OOP in Ruby
  • * Functions are declared outside classes. 

So as you can see, the difference is not too much, it is more about where they're located inside the program, but they do the same: encapsulate Ruby code.

I hope you learned a lot from this blog post.

Thanks for reading
Daniel Morales