by phorneker
In the last issue, we discussed the basic structure of a Ruby program, integers and floating point numbers, logical and mathematical operators, strings, and the if/then/else statement.
Control Statements
The elsif keyword is a shortened form of the else if keywords in a control statement. Hence, we could have written this fragment of code as:
Ruby provides a unless statement that executes code if the condition associated with the unless statement is false. The fragment of code here would then be written as:
Note that the keyword then is optional for the unless statement, yet it is required for the if statement. This makes sense from a readability perspective.
One interesting note on the if and unless statements is that they can be used as modifiers to other statements, such as puts and print.
Consider this block of code:
We could write this fragment of code as follows:
...and it would be easy to see the logic behind this fragment of coding, not to mention that the same thing was accomplished in only two lines of code rather than five in the fragment before this.
For this fragment of code, only one of these statements will be executed, and which one depends on whether validnumber is true or false.
But, what if validnumber is set to nil? In Ruby, the boolean data type for variables is not truly binary. The existence of nil provides a third condition to test for. Technically, nil is neither true nor false. The above fragment will work correctly if nil is assigned to validnumber as the unless statement tests to see if the condition specified is false.
For the current version of Ruby, nil and false are considered to be the same for the unless statement. However, this may change in future versions of Ruby.
|
Another Way to Code If/Then/Else Statements
Let us take another look at the previous fragment of code:
As with most any statement, method or variable, the if/then/else statement can return a boolean value that can be assigned to a variable. Hence, this fragment of code can be written as follows:
...and while we are at it, we can take this one step further.
We have managed to accomplish the same task in this one line of code whereas previously, we did it in nine lines of code in the original fragment.
Now, let us revisit the program from the first article (the one called numbertest.rb):
Given what we now know, we can rewrite the program as follows:
This is one example of the productivity that can be achieved with the Ruby programming language, though the former is far more readable if you are used to programming in languages like Pascal.
Here, we managed to reduce the size of the source code example from 19 lines to a mere 8 lines, and still achieve the same result.
Case Statements in Ruby
The case statement is an extension of the nested if/then/else statements. The structure of a case statement is as follows:
For any case statement, there is the end statement to tell Ruby that this is the end of the case/when/else statement, and at least one when block and exactly one else block is required, with the else block placed at the end of the case/when/else statement just prior to the end statement. The else block contains code to be executed when all conditions contained in all when statements are false (i.e. when all else fails).
Conditions, shown here with the <valuex> labels can be a single value, or a range of values.
Let us say, we want to define a range from 90 to 100. In Ruby, this range is coded as
90 .. 100
The spaces between the two dots and each of the numbers are required to tell Ruby that we are defining a range of values.
On to the example.
One good use of the case statement would be to create a function that returns a letter grade representing a test score (from 0 to 100). Assuming the standard grading scale used for many years, i.e. that passing starts at 60, we would then define ranges as follows:
This is a rather simplified form of a typical grading scale. We could define this further.
I know that in practice, some schools consider 70 to be passing instead of 60, but for purposes of demonstrating the case statement, this grading scale will be used.
As we can see here, the ranges here are clearly defined. For each when statement, we could execute any code we wanted. Here, we will simply print out the appropriate grade for the grade value passed to the case statement.
More operators
The when portion of the case/when/else statement is equivalent to
<value> === <range>
which tests value to see if it is contained within range, and will return true if the contents of value are included in the set of values represented by range.
In contrast, <value> == <range> simply tests to see if the contents of value are exactly the same as the contents of range, which by definition will always return false as individual values will never be the same as a range of values, even if the range contains only one value.
On the other hand, <value> = <range> simply assigns the contents of range to value only if value happens to be the name of a variable. If value happens to be a constant or a data type incompatible with the type being assigned, an error message would result, as such an assignment would not make sense.
Likewise, <value> !=== <range>, as one would expect, tests to see if the contents of value are not included in the set of values represented by range.
Looping in Ruby
In C, C++, and even BASIC, we have the for statement to execute a block of code inside a loop. Ruby has this function, too. The for statement requires a range of numbers and a variable for parameters.
What sets Ruby apart here is that the range does not have to be all numbers. Ranges specified in Ruby can be of mixed data types, a feature not found in traditional programming languages.
The for statement in Ruby is coded as follows:
for <variable> in (<range>)
<block of code placed here>
end
The things that are mandatory are variable (which does not have to be used in the loop, but most likely should depending on the code being executed), and the range, which can be almost anything you could imagine, as long as it makes up a list, including a list of ranges.
For example:
for color in (black, blue, green, cyan, red, magenta, yellow, white)
puts(color)
end
will display the names of the basic colors in an RGB palette.
(You can emulate this in Pascal by defining a custom data type in the variable declaration sections.)
The while statement is very much the same in Ruby as it is in C and C++, i.e.
while <condition>
<body of code placed here>
end
C, however, requires the keyword do, whereas do is optional in Ruby. It is there for those of us who are used to writing in C.
For the while statement to be executed at least once, the value of condition must be true at some point. If the value of condition is always false, the code inside the loop will never execute.
Likewise, if the value of condition is always true, then you have just coded an infinite loop.
|
The while statement executes code inside the loop when the value of condition is true, whereas the until statement executes code inside the loop when the value of condition is false.
until <condition>
<body of code placed here>
end
which is basically the equivalent to
while !<condition>
<body of code placed here>
end
Did you know that while and until loops can be coded a second way?
begin
<body of looped code placed here>
end until <condition>
is the same as
until <condition>
2body of code placed here>
end
...and the same follows for the while statements
begin
<body of looped code placed here>
end while <condition>
is the same as
while <condition>
<body of code placed here>
end
Give Looping a Break
Ruby code inside the while and until statements have the potential to become what we call infinite loops, or blocks of code that continue to execute endlessly (or until something causes the program to crash, such as a kill command from the superuser).
Ruby offers a generic loop statement that does just that and does not require any parameters to boot.
loop
<body of code placed here>
end
The break statement provides a way for the program to exit from such a loop.
The infinite loop is useful in some instances, such as implementing the main loop in a command line interpreter.
It is a better programming practice to use the while and until statements to create the loop, then use a flag to indicate when to exit that loop, rather than relying on a generic loop statement and then placing a break statement to end the loop.
The If/Then Statement Redux
Ruby provides yet another form of the if/then statement. This one does not include the keywords if or then, but instead consists of two operators, namely "?" And ":", and can be used in place of a variable, or assigned to a variable.
If you have programmed in C or C++, this form should be familiar to you. Let us go back to a previous code fragment.
if validnumber then
puts "You have typed in the number #{number}"
else
puts "Sorry, what you typed in is not a valid number"
end
This can be written as follows:
puts validnumber ? "You have typed in the number #{number}" : "Sorry, what you typed in is not a valid number"
(Note: This is all one line of code.)
The first argument after the "?" is what is displayed if the value of validnumber is true, otherwise the second argument, the one after the ":" is displayed.
Program Flow
As with any interpreter, Ruby programs normally start at the beginning of the source file and flow down to the end of the source file. Unlike C and C++, there is no main function or statement. Unless functions and methods have been defined in the source code, what would be considered the main function is the first line of Ruby code not contained in a separate block.
Consider the code contained in our numbertest.rb (after simplification from what we discussed earlier)
#!/usr/bin/ruby
print "Please type in a number "
name = gets
number = name.to_f
validnumber = if (number != 0.0) || (name == "0.0") || (name == "0")
puts "Sorry, what you typed in is not a valid number" unless validnumber
puts "You have typed the number #{number}" if validnumber
end
This program starts at the print statement and ends where it says (appropriately) end.
If we were to define a function (or a method) in this source code file, the program would still begin at the first print statement as the code between print and end has not been enclosed inside a block.
What Exactly Are Blocks?
Before we continue, we need to know what exactly is a block with regards to Ruby.
Think of a block as a fragment of Ruby code enclosed between two statements, one marking where execution should start, and one marked end where execution should end.
A generic block in Ruby is coded as follows:
begin
<code to be executed here>
end
Simple enough, isn't it? Most likely, there is a control statement such as a for, if/then/else, while, until, or unless that replaces the begin in this statement. (We have already seen examples of this in this article.)
Blocks are also found in when statements contained in case statements. In the case of the when statement, execution of that block of code continues until another when statement is encountered, an else statement is encountered, or when it comes to the end statement. In any case, execution jumps to the code after the end statement that ends the given case/when/else statement.
Where does a Ruby program begin?
As Ruby does not explicitly have a main() function like in C and C++, execution of a Ruby program normally begins at the beginning of the source file at the first executable statement not contained in a block. (This is why I defined what a block is before writing this section.)
Ruby, however, does not always start program execution this way. You can specify where in the source code file Ruby is to begin execution and where the program ends by using the BEGIN and END statements.
How is BEGIN different from begin? You type BEGIN, in all capital letters. Everything in Ruby is case sensitive, (not unlike C, C++ and Java), hence BEGIN is not the same as begin.
Language Note: Pascal, unfortunately, does not make this distinction as the language was developed before case sensitivity was even a consideration.
|
Hence, we could write our example program as:
#!/usr/bin/ruby
BEGIN
print "Please type in a number "
name = gets
number = name.to_f
validnumber = if (number != 0.0) || (name == "0.0") || (name == "0")
puts "Sorry, what you typed in is not a valid number" unless validnumber
puts "You have typed the number #{number}" if validnumber
END
...and still get the same result. Most of the time, we will not need to do this when writing simple Ruby code.
Error Handling with Rescue/Retry
Sometimes, when executing a program and you enter a value that generates a runtime error, it is not appropriate for Ruby to halt execution of the program simply because an invalid value was entered. To handle this potential situation, we define a block of code as follows:
<
rescue
<do error handling code here>
retry
end
Anything placed here is executed only if a runtime error occurs during execution of a program, such as an attempt to divide by zero, or asking for the square root of negative one (in the latter case, this would not generate a runtime error if we define a module to handle imaginary numbers as the square root of -1 is i. But that is a topic for another article.)
Otherwise, execution of the code jumps over this block of code.
If this code does get executed, control of the program returns to the point where the runtime error occurred.
If you are familiar with some versions of BASIC, this is similar to the ON ERROR GOTO statement with the RESUME statement at the end of the error handler.
|
Methods ARE Functions in Ruby
What is the difference between a method and a function? In Ruby, there is no difference. The difference here depends on whether you are used to object oriented programming, or structured programming.
What I refer to as a function in Ruby is actually a method. This is because I was educated in the structured way of programming (before there was such a thing as object oriented programming), where there is only one point of entry in a program, procedure or function, and one exit point for the same program, procedure or function.
While it is possible to write Ruby code in a structured manner, it is not as efficient or productive as writing code in Ruby code in an object oriented manner, as the language was designed as an object oriented language.
As I learn Ruby, I am rediscovering programming techniques that were used for writing applications for vintage computers from the late 1970s through the early 1990s, where memory usage and disk space were at a premium. In addition, I am also learning about what makes object oriented programming languages (such as Ruby) a popular choice for software and internet developers.
Ruby requires that functions be defined before they are used. This makes sense as it is true of just about every other programming language available today.
But that was not always the case.
Before high level languages (i.e. languages such as FORTRAN, Pascal, C, C++, PL/I (which is making a comeback) and BASIC came into existence, programs were developed in assembly language, which meant learning about individual microprocessors and their instruction sets.
Once a program started at a specific address in memory, execution was linear until an instruction to jump was executed, with the location being the next two to four bytes in memory.
If functions were to be defined before they were used, you would have to know the exact location in memory where the program begins, and place a jump statement at the beginning of where the processor expects the program to begin so the processor can start the program at the proper location.
As a result, to increase efficiency of the machine language program, functions and procedures in assembly language were typically placed after the main program.
|
In Ruby, methods (or functions) are coded as follows:
def <function name>
<place code here>
end
That's it. Typically, code is indented (two or three spaces for readability) to show that this code is a part of a defined function, and not the actual Ruby program.
The return keyword is used when a method is to return a value to the calling program or method. Methods always return some kind of value. If the return keyword is not used in a defined method, the method will return nil, meaning no value is returned, and its boolean value is considered to be neither true nor false for most boolean functions and operators.
But wait. There's more!
Ruby provides two more keywords typically used in loops, namely redo and next.
The keyword redo tells Ruby to restart the current loop block using the same value in the iterator variable.
The keyword next tells Ruby to skip the code between the statement containing next and the end of the loop, then increment the iterator variable by one.
These are those things that are not really needed most of the time, but are available in case we come to a situation where such things are required.
I wanted to get to the string manipulation in Ruby, but in my research, I found that there is more than enough material (including many Ruby methods) to warrant an article dedicated to this topic.
As Ruby is an object oriented language, I would need to get to Classes (where functions and procedures are called methods) first.
What I just demonstrated is the use of Ruby as a language for structured programming.
Now we can guess what I am going to have to cover next.
The phrase "But wait. There's more!" became one of my favorite expressions after watching Jaw Tooth's railroad videos on YouTube. In most of his videos there is always additional material added to the video. This material is introduced with Jaw Tooth saying "But wait! There's More!" followed by the additional material.
|
|