Books / Ruby for Beginners / Chapter 15
Methods in Ruby
Methods (or functions) are usually small chunks of code that you can reuse. Until this moment we haven’t reused the code that we had written (expect for code inside of blocks, like in loop do...end
). But with methods we can improve and optimize our programs by splitting it into multiple logical blocks.
Methods shouldn’t necessarily make our program shorter. The main idea is to define logical parts and make a program more readable for a human. Sometimes this process is called “refactoring” (there are many refactoring techniques, and this particular technique is called “extract method”). The result this refactoring is logical blocks of code extraction into one or more methods, so this code can be reused later in a program again.
But we can introduce methods for our own convenience. Do you remember we were using the following code to convert user input to a number?
age = gets.to_i
Code above gets user input and converts it from String to Integer (with .to_i
) and saves result to age
variable. This expression doesn’t look self-explanatory to someone who is looking to the code for the first time. Let’s improve it a bit, so it will (hopefully) become easier to understand:
def get_number
gets.to_i
end
age = get_number
Code is the same, but on lines 1-3 we defined a method with def...end
syntax. This method won’t get executed, until we explicitly call it (on line 5 in our case). Now it looks little bit easier, especially if we want to initialize multiple variables this way:
age = get_number
salary = get_number
rockets = get_number
Methods in Ruby always return value, even if it seems they don’t return any. Result of the last expression is the return value for a method. In our case above we have only one line inside of get_number
method (gets.to_i
) and since it’s the last line, method get_number
will return result of this expression. But if for some reason we want to return value earlier, we can use return
keyword:
def check_if_world_is_crazy?
if 2 + 2 == 4
return false
end
puts "Jesus, I can't believe that"
true
end
Last line can be written as return true
, but it’s up to you (and coding conventions, Rubocop is the right tool to check your style). Method can also accept parameters:
def get_number(what)
print "Enter #{what}: "
gets.to_i
end
age = get_number('age')
salary = get_number('salary')
rockets = get_number('number of missiles to launch')
Result:
Enter age: 10
Enter salary: 3000
Enter number of missiles to launch: 5
Would you agree that program above looks much more readable than the following?
print 'Enter age:'
age = gets.to_i
print 'Enter salary:'
salary = gets.to_i
print 'Enter number of missiles to launch:'
rockets = gets.to_i
Ruby Methods Example: Judgement Day Emulator
Let’s practice a bit to sum up everything we know about methods. The machines have taken over the world. The is a struggle for survival. Who will survive: humanity or machines? Now it’s up to destiny to decide our future. Well, actually up to the random number generator.
Program will show the steam of upcoming events happening in the world. It would be much more interesting if we could do it by using some graphics, but in our case it will all depend on observer’s imagination. One may like or program and even use it as screensaver.
Important note: this program can be done the different and better way. But we still have limited Ruby knowledge, so our goal is more to practice, rather than delivering a piece of art.
First, let’s agree that there are only 10000 humans and the same number of machines left. With every iteration we’ll have only one random event. And the number of humans and machines will be decreasing with the same probability. Victory is when there are no more machines (or humans) left. Let’s proceed with a solution.
We’ll start with definition of victory and the main loop with two variables:
humans = 10_000
machines = 10_000
loop do
if check_victory?
exit
end
...
end
Variables humans
and machines
represent the information about survivors.
Method check_victory?
returns Boolean type value if one side has won (it doesn’t matter which one). If there is a victory, we just exit
the program. If there is no victory, we continue iteration inside of infinite loop. Let’s also print information about who actually won the battle inside of this method.
Now we need to define a couple of events that can happen. Let’s call them event1
, event2
, and event3
. We’ll be calling this or another method depending on random number provided by rand
. It’s like throwing dice with only 3 distinct values from 1 to 3:
def event1
# ...
end
def event2
# ...
end
def event3
# ...
end
# ...
dice = rand(1..3)
if dice == 1
event1
elsif dice == 2
event2
elsif dice == 3
event3
end
We used elsif
keyword above (we’re already familiar with else
). elsif
is one of the less intuitive abbreviations in Ruby, and it just means else if
.
And we’ll finish our loop with sleep
statement, which will delay the execution of our program by certain amount of seconds (from 0.3
to 1.5
):
sleep rand(0.3..1.5)
Complete version of the program:
########################################
# DEFINE VARIABLES
########################################
@humans = 10_000
@machines = 10_000
########################################
# AUXILIARY METHODS
########################################
# Method returns random value: true or false
def luck?
rand(0..1) == 1
end
def boom
diff = rand(1..5)
if luck?
@machines -= diff
puts "#{diff} machines destroyed"
else
@humans -= diff
puts "#{diff} humans killed"
end
end
# Method returns random city name
def random_city
dice = rand(1..5)
if dice == 1
'San Francisco'
elsif dice == 2
'Moscow'
elsif dice == 3
'Beijing'
elsif dice == 4
'London'
else
'Seoul'
end
end
def random_sleep
sleep rand(0.3..1.5)
end
def stats
puts "#{@humans} humans and #{@machines} machines left"
end
########################################
# EVENTS
########################################
def event1
puts "Missile launched against #{random_city}"
random_sleep
boom
end
def event2
puts "Nuclear weapon used against #{random_city}"
random_sleep
boom
end
def event3
puts "Group of soldiers have breached defence in #{random_city}"
random_sleep
boom
end
########################################
# CHECKING VICTORY
########################################
def check_victory?
false
end
########################################
# MAIN LOOP
########################################
loop do
if check_victory?
exit
end
dice = rand(1..3)
if dice == 1
event1
elsif dice == 2
event2
elsif dice == 3
event3
end
stats
random_sleep
end
Sample result:
Nuclear weapon used against London
3 machines destroyed
10000 humans and 9997 machines left
Group of soldiers have breached defence in London
2 machines destroyed
10000 humans and 9995 machines left
Nuclear weapon used against Seoul
4 humans killed
...
Exercise 1 Implement
check_victory?
method (now it just returnsfalse
). In case of victory it should show information about who actually won the battle and who lost. Replace10_000
with10
so it will be easier to work on this program (you won’t need to wait too long to see how your method works).
Exercise 2 Lookup documentation for “ruby case statements” and replace
if...else
withcase...when
.
Exercise 3 Improve the program, so every iteration we not only lose, but also get random number of humans and machines. Theoretically, this battle may never end, but with low initial number of humans and machines at the beginning (like 10 or 100 instead of 10000) there can be a case when one side randomly wins.
Exercise 4 Improve the program and add at least 3 events, so your output looks more interesting. Use your imagination.