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, some JS stuff and more...

Github:
/danielmoralesp
Twitter:
@danielmpbp

2024-11-05

Ruby Functions

So far we’ve been seeing the way 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. Now we need to see a tool that can help us organize all of 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. By calling a function, we are calling all those lines at once, without having to repeat the code. A function is a tool that you can use over and over again to produce a consistent result from different inputs. If you see the previous definition about functions it is easy to figure out why it is so popular among developers, programming languages and the utility of it.

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 block of code. The header starts with the word “def” and the function name. If the function receives a parameter you can put parentheses after the name of the function, if you don’t receive parameters it is not necessary to have parenthesis (we'll see what's a parameter later on this same blog post). The body of the function runs some kind of operation. The syntax looks something like the following:

Note: we’ll be using the text editor and executing 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? It will print the string “Hello World!”? Let’s see

It doesn't print out anything, but why?

This is because the function acts like a container of code, but you need to call that container to be executed. If you don’t call the container of code you won’t see any result.

Calling Functions
We can say that Functions are divided in 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 execute in any manner. The way we call a function is 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 we 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 who contains the logic of our program

Parameters/arguments
The next construction block inside the functions are 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 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 parenthesis, and we can give any name we want (unless you use a reserved word). We have to 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 pass 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 we 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’re not having any errors, but when we declare a parameter is normally because we’ll be using it inside the function to do something dynamically. 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 Single Responsibility principle which says that each function should have just one responsibility and if your functions are receiving a ton a parameters (like 6, 8, 10 or more) probably you’ll doing a lot of things inside just one function, and is probably better to separate concerns and create 2 or more functions derived from this bigger one and then connect all of them and making calls between them accordingly

Similar situations as the one mentioned above, if we’ve declared two parameters and then 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 about the order of the parameters when calling them, because we’ll have wrong behaviors inside the code execution. 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 different order we can use keyword arguments. Arguments are synonymous with parameters, so don’t be confused by 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’ve the name of the function 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 little change: we call first the name given to each parameter followed by a colon “:” and then given the value for that parameter. 

At 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 right order when we use keyword arguments. 

Default arguments/parameters
The other situation that we can have is if we want a default parameter, because it is somewhere standard, but it changes just sometimes. Here we can use the default parameters and we can use the next 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 then by a value. This value will be given to the function in case we don’t pass anything from the function call

Now 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 take care about the result we want to return from our function. This means that functions normally 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 for 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 make a change in 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 what’s the output. 

* First line keep 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 we assign that function to a variable. This is new for us. Can we assign 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 body of the function. We can call this as "implicit return", because we don’t use the keyword “return” to call 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 now 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. 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 the way we declare objects in OOP in Ruby
* Functions are declared outside classes. 

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

I hope you learned a lot with this blog post

Thanks for reading
DanielM