Books / Ruby for Beginners / Chapter 12

Variables Comparison in Ruby

One of the foundations of any programming language is variable comparison. Depending on comparison result we can execute this or another part of a program. Example scenario: if age of the user is less than 18, ask for additional information or block access for this user.

There is a special vocabulary around this functionality, let’s take a closer look:

  • Branching. It is implied that a program may have multiple branches, chunks of code, which can be executed if certain condition is met.
  • Branch , block, code block – usually few lines of code which can get executed (or not) under certain conditions.
  • Comparison, testing, test, “if statement”, condition – comparison itself, just checking if variable/value equals, or not equals, greater, less than, truthy, falsy, and so on. Experienced programmers often use word test, which basically means “testing the variable for certain value”. In Linux (MacOS, or POSIX-compatible) operating systems, one can get documentation about testing variables in shell (not Ruby):

      $ man test
      ...
      test - check file types and compare values
    

Later in this book we’ll cover very basic unit testing and create our own tests. Those will be also tests, but different kind of tests. Usually unit tests have multiple lines of code, and variable testing is something really easy, one-two lines of code.

Let’s see how variable comparison works:

puts 'Your age?'
age = gets.to_i
if age > 18
  puts 'Access granted'
end

Result:

$ ruby app.rb
Your age?
20
Access granted

$ ruby app.rb
Your age?
10

We used “if” statement to compare variable age and value 18. If expression age > 18 is true, then our block gets executed. This block on line 4 has only one line, but we can add more if we want. If expression age > 18 is false, then block inside won’t get executed.

Usually we use two space indentation for all blocks. It won’t affect the behavior of a program, but will make it more readable. Also, tools for code analysis, like Rubocop, will give you a warning if you won’t follow this rule.

Now we’re getting closer to the data type we’ve discussed in previous chapters. Let’s see what it is with REPL:

$ irb
> true.class
 => TrueClass
> false.class
 => FalseClass
> true.is_a?(Boolean)
[ERROR]

Yes, we know that there is no single type for “true” and “false” in Ruby. There are TrueClass and FalseClass, but these classes have the same functionality, only the behavior is completely opposite. In C language “true” and “false” are represented as “int” (integer).

There are few ways to compare variables or values, here is the list of comparison operators:

  • > - more than
  • < - less than
  • == - equals
  • != - not equals
  • >= - more or equals
  • <= - less or equals
  • <=> - (only in Ruby) spaceship operator. Yes, it looks like space ship! We won’t be covering it, but it can be useful when you implement sorting. For example, you want to sort an array of animals by amount of ears each animal has.
  • === - (only JavaScript) strictly equals
  • !== - (only JavaScript) strictly not equals

JavaScript is quite interesting, isn’t it? There are two types of comparisons: strict (===) and the more commonly used (==, also known as “type-converting comparison”). With less strict comparison we can compare two different types like strings and numbers, ducks and ostriches (they’re all birds), and so on:

$ node
> '5' == 5
true

But with strict comparison in JavaScript we can compare only objects of the same type:

$ node
> '5' === 5
false

Luckily, Ruby has only one type of comparison, and if you compare objects of different types, result will be always false:

$ irb
> '5' == 5
 => false  

By the way, there was one intentional mistake in our program at the beginning on this chapter, can you spot it? We had age > 18 condition, when actually we need age >= 18, because we want to include users of age of 18 and want allow them to visit our imaginary website.

If condition is simple, we can use so-called one-liner. It’s the syntax that has two parts, but written as one line:

exit if age < 18 

The code above looks like plain English sentence: “exit if age less than 18”, which makes our program even more readable. But in this case we just exit the program without any messages. If we want to add condition, our branch will take 2 lines (line 2 and line 3):

if age < 18
  puts 'Access denied'
  exit
end

Sometimes one-liners make sense and make your program more readable. Moreover, some of them look like plain English sentences, what could be easier?

Exercise Fill out four tables below, try to figure out what would be the result (true or false) for each condition? Check your answers by executing expressions in REPL.

Table 1:

Expression: 1 > 1 1 < 1 1 >= 2 1 == 1 1 != 1
Your answer:          

Table 2:

Expression: 1 > 2 1 < 2 1 <= 2 1 == 2 1 != 2
Your answer:          

Table 3:

Expression: true > false false > true true == true
Your answer:      

Table 4:

Expression: false == false false != true
Your answer:    

Combining Conditions

We can combine conditions that go right after “if“-statement. Sometimes we need to perform multiple checks:

if ...
  puts ...
end

There are two ways of combining conditions: “AND” and “OR”. Each way can be represented by characters && (double ampersand) and || (double pipe). Example in REPL:

$ irb
> 1 == 1 && 2 == 2
 => true
> 1 == 5 && 2 == 2
 => false
> 1 == 5 || 2 == 2
 => true

Note that you can also use and keyword instead of && and or instead of ||, try it yourself with example above! However, tools like Rubocop do not recommend doing that: “The minimal added readability is just not worth the high probability of introducing subtle bugs”.

First example above is quite clear, we just check if “one equals one and two equals two”, result should be “true”.

Next condition is more interesting, first part checks if “one equals give”, which gives us “false”, and the second part checks if “two equals two”, which is “true”. The final condition sounds like “Is false and true gives true?”. It’s like asking two of your friends, first lies, and another is telling truth, will at the end you’ll give 100% truth? No, you won’t. In fact, the second condition will be never executed if first condition is false. Ruby interpreter will just save your resources, because you use &&.

Third condition is almost the same, there are two parts: “false” and “true”, but we’re using || (“or”) to combine these conditions. So here we’re interested in one OR another result. It’s like about buying something expensive for your own birthday, and you’re asking your friends for advice. If only one of them says “yes”, you gonna buy this new guitar you always wanted.

Let’s look at the real program:

puts 'Your age?'
age = gets.to_i
puts 'Do you agree to our terms and conditions (y/n)'
answer = gets.chomp.downcase
if age >= 18 && answer == 'y'
  puts 'Access granted'
end

Result:

$ ruby app.rb
Your age?
21
Do you agree to our terms and conditions (y/n)
n

$ ruby app.rb
Your age?
21
Do you agree to our terms and conditions (y/n)
y
Access granted

So one should type the age and agree to terms and conditions to visit our imaginary website. Then program checks if age is 18 or above, and if the user agrees to terms and conditions. If all these conditions have been met, user is granted access to the website. Note that “18 or above” was specified with >=. We could also do it another way:

if (age > 18 || age == 18) && answer == 'y'

Exercise 1 Try to figure out what would be the result (true or false) for each condition below? Check your answers by executing expressions in REPL.

Expression: 0 == 0 && 2 + 2 == 4

Your answer:

Expression: 1 == 2 && 2 == 1

Your answer:

Expression: 1 == 2 || 2 == 1

Your answer:

Exercise 2 Write a program that will ask for login and password. If login is “admin” and password is “12345”, it should print “Granted access to online banking”.

Exercise 3 You’re writing a program to sell a land on the Moon. Any land of less than 50 square meters costs $1000. Land from 50, but less than 100 square meters will cost $1500. And 100 square meters or above is $25 for one square meter. Write a program that asks width and length of the land user wants to buy, calculates the area, and shows the price of the land.

Useful Methods of Ruby Language

Do you remember this program from previous chapter:

puts 'Launching missiles...'
5.downto(0) do |i|
  puts "#{i} seconds left"
end
puts 'Boom!'

It just counts from five to zero and shows “Boom!” message. When user runs this program, he or she gets results instantly, because there is no any delay. It should count seconds, but all programs run as fast as it is possible, unless otherwise specified. Ruby doesn’t know that we count seconds, because “seconds left” is just a string. We can type any string we want. So we need to explicitly specify the real delay. Let’s do it now:

puts 'Launching missiles...'
5.downto(0) do |i|
  puts "#{i} seconds left"
  sleep 1
end
puts 'Boom!'

Method “sleep” takes one parameter - the number of seconds it should “sleep”. You can also specify fractional numbers. For example, 0.5 is half of a second (500 milliseconds).

Programmers usually don’t use “sleep” in real programs too often, because programs should execute as fast as it’s possible, right? But often we can use it while testing our software. For example, we can create the following test: “type login, password, click on the button, wait 10 seconds to see if everything’s okay”. But this approach is also questionable, and some programmers don’t like it, they say if test requires “sleep”, then it means something is fundamentally wrong. But from author’s experience it’s not always possible to get rid of “sleep”, sometimes it’s required.

Interesting detail is that there is no “sleep” in JavaScript, because this language is asynchronous by its nature. “Sleep” call (in all languages) is blocking call. In other words, it blocks the flow of the program for certain number of seconds. But this is exactly what JavaScript designers were trying to avoid, they didn’t want to introduce any blocking calls, in other words everything in JavaScript is asynchronous. It doesn’t mean you can add delay, you just do it the different, non-blocking way. Sleep in JavaScript is little bit more complicated comparing to Ruby.

If JavaScript program should work without any blocking calls, then it means it’s true for absolutely everything, not just for “sleep”. Imagine that we need to read (or copy) a huge file into memory from disk. But we can’t block, and reading a huge file takes time. What would you do and how would you solve that? Engineers of JavaScript introduced so called “callbacks”, something that gets executed at the end of operation. You don’t need to remember that, but we’ve decided it’s worth explaining how it works, because 99% of Ruby programmers will need to write JavaScript code one day.

Here is the simplified version of our Ruby program above:

puts 'Launching missile, get ready...'
sleep 1
puts 'Warning, 1 second has passed, launching now...'
puts 'Boom!'

Look at how incorrect (but exactly the same) program in JavaScript can be implemented by unexperienced programmer:

console.log('Launching missile, get ready...');

setTimeout(function() {
  console.log('Warning, 1 second has passed, launching now...');
}, 1000);

console.log('Boom!');

Output of incorrect program:

Launching missile, get ready...
Boom!
Warning, 1 second has passed, launching now...

In other words, we’re getting a warning about missile launch after the missile has exploded already. But that’s not right! And that’s why we need to think asynchronously in JavaScript. Actually, it’s easier than it looks from the first sight, and for successful launch we need to move last line to setTimeout block:

console.log('Launching missile, get ready...');

setTimeout(function() {
  console.log('Warning, 1 second has passed, launching now...');
  console.log('Boom!');
}, 1000);

But imagine we need to wait one more time, like this:

puts 'Launching missile, get ready...'
sleep 1
puts 'Warning, 1 second has passed, launching now...'
sleep 1 # This line was added
puts 'Boom!'

Correct JavaScript program will look like:

console.log('Launching missile, get ready...');

setTimeout(function() {
  console.log('Warning, 1 second has passed, launching now...');
  
  setTimeout(function() {
    console.log('Boom!');
  }, 1000);

}, 1000);

Our JavaScript code is becoming more nested while we keep adding more asynchronous operations, and our Ruby program remains “flat”. It is one of the fundamental differences between synchronous (Ruby) and asynchronous (JavaScript) languages, and one of the reasons why you need to understand synchronous language before you write any code with asynchronous.

When we add more blocking calls to JavaScript, we have more callbacks, code becomes more nested, less readable, and programmers call it “callback hell”. There was attempt to solve “callback hell” in newer version of JavaScript called ES6 (ECMA Script version 6) with new await keyword. Program remains flat, but programmer still needs to understand underlying asynchronous concepts. JavaScript was invented long time ago and it’s improving over the time, but sometimes it isn’t something one can easily grok in 5 minutes. You’ll definitely need more patience with JavaScript than you need it with Ruby.

However, learning minimal JavaScript isn’t a big issue for any Ruby programmer. It’s quite easy to tackle fundamentals by your own or over the time for two reasons: 1) Ruby gives good foundation for understanding other languages 2) Want it or not, all web developers will deal with JavaScript code time to time, just make sure you read documentation and don’t delay your minimal JavaScript education.


Licenses and Attributions


Speak Your Mind

-->