banner
Previous Page
PCLinuxOS Magazine
PCLinuxOS
Article List
Disclaimer
Next Page

The Ruby Programming Language: Writing A Ruby Program


by phorneker

Last time, I introduced you to the Ruby Programming Language by way of the Interactive Ruby interpreter. Towards the end of the article, I gave you a traditional "Hello World" program and introduced you to class implementation. That took a fair amount of typing to accomplish what we did. Now, instead of having to type that code in Interactive Ruby every time, we can create a source code file in the Ruby language using any text editor available in the PCLinuxOS repository, be it kate, gedit, or even EMACS, the latter of which I prefer to use for writing source code that is not going to be part of a website. (For HTML, I use Bluefish.)

Let us take that classic "Hello World" program we wrote in the last issue.

def hi(name = "World")
puts "Hello #{name.capitalize}"
end

Instead of having to type in all of this into irb every time we launch irb, would it not be easier to simply type this program into an editor and save it as helloworld.rb? That is exactly what we are going to do.

Ruby source files have .rb for a file extension, so there is no doubt that this is a source code file that was written in Ruby.

If you are using a desktop such as Plasma 5, XFCE, MATE, or even LXDE, double clicking on an icon representing that file will associate the file with the installed Ruby interpreter (unless you changed the association to do something else, such as assign the Ruby source file type to a text editor for editing that file).

First, we launch a text editor.

All Ruby source files start with the following line to indicate that this is a source code file written in Ruby.

#!/usr/bin/ruby

This is the location where the Ruby interpreter binary is located after installing the ruby package from the repository.

I have seen this statement written as #!/usr/local/bin/ruby on systems where Ruby was compiled from source code using the standard configure, make and then make install method associated with compiling from source code tarball packages. But, for PCLinuxOS, if you installed the ruby package from the repository, the ruby binary will always be located at /usr/bin/ruby.

Now, let us save the file.

For this article, I created a ruby directory within the home directory on my laptop. So, the Hello World program is stored at /home/patrick/ruby/helloworld.rb, which now contains the following:

#!/usr/bin/ruby

def hi (name = "World")
puts "Hello, #{name.capitalize}"
end

The hashtag (#) at the beginning of any line is used to comment or otherwise make notations on the source code so that others who read this source code will know what is going on in this particular program, function, method, class or even a entire Ruby library.

The first line is a notable exception to this rule. The #! indicates that the remainder of this line points to the location where the interpreter or shell binary is located, such as the Ruby interpreter, followed by any parameters passed to the interpreter or shell.

#!/usr/bin/ruby

def hi (name = "World")
puts "Hello, #{name.capitalize}"
end

There is a (optional) parameter that is passed to the function hi called name and has been assigned the string value of "World". This value is used only if the function hi is called without any parameters passed to that function.

The #{name.capitalize} within the quotes in the puts statement is a placeholder that first, makes sure that name is properly capitalized before displaying the contents of name.

As everything in Ruby is an object, variables are no exception, and task of the capitalize method of the name variable (as an object) is to make sure that the value assigned to name is properly capitalized. (Yes, it is that simple.) The capitalize method is one method used by Ruby for its string handling functions.

This program is not yet complete. We need to invoke this function, with a statement called (what else?) hi.

I have included two versions of this statement to show you the two ways this function can be called.

In the second version, be sure to place quotes between the name, or you will get a runtime error when you run this program.

#!/usr/bin/ruby

def hi (name = "World")
puts "Hello, #{name.capitalize}"
end

hi()
hi("Patrick")

Now save the file and exit your editor. We can now open a terminal and look at the file(s) we created. Simply type ruby helloworld.rb and press the Return/Enter key.

[patrick@localhost ruby]$ ruby helloworld.rb
Hello, World
Hello, Patrick
[patrick@localhost ruby]$

We have just successfully created the Hello World program in Ruby. The first version of the hi function was called without any parameters. Since the default value for name was assigned the value "World", that is what was used for the parameter. As this was a script that ran on the Ruby interpreter instead of Interactive Ruby, we did not see the "=> nil" that would normally appear.

Bonus Tip: If you wish to execute the script without having to explicitly type "ruby" on the command line, simply type chmod a+x helloworld.rb on that command line. You will, however need to type ./helloworld.rb to execute the script.

Another Bonus Tip: If you have a folder in your home directory, move this file to that directory, then all you have to do is type helloworld.rb to execute the script.


A COMMENT ON COMMENTS

I mentioned that the hashtag (#) is used for placing comments in Ruby source code. This is only one way to accomplish this task. Comments with a hashtag at the beginning of the line are ignored by Ruby for that particular line of Ruby code. Placing a hashtag at the end of a function or statement in Ruby causes Ruby to treat the remainder of that particular line of code as a comment at the same time interpreting the Ruby code that preceded the hashtag, unless that hashtag is inside quotes in a statement such as puts.

In that case, the hashtag is part of a placeholder containing the name of the variable to be used (as well as any methods that modify the variable) when interpreting what is in the quotes. In the case of puts, the placeholder substitutes the value contained in the variable for the name of the variable. For example:

puts "Hello, #{name.capitalize}"

In this example, the value contained in the variable name is substituted for the placeholder. Had this example read puts "Hello, {name.capitalize}", we would have gotten something we did not expect. Suppose we appended another hashtag at the end of this statement.

puts "Hello, #{name.capitalize}" # This is a Hello World program

Ruby would interpret the statement the same way as before. For the second hashtag, the words "This is a Hello World program" are treated as a comment, and hence, are ignored by the interpreter.

Now, what is we want to place internal documentation, i.e. comments that take up more than one line of text in the source code. We could start each line with a hashtag...or we could format the comments this way:

= begin

Comments placed here, as many lines as you like

= end

...which as we can see is far more readable for us, especially when we review the code sometime in the future.

In Ruby, everything between the "= begin" and the "= end" is interpreted as program comments (aka documentation), and hence is ignored by the interpreter.


VARIABLES ARE OBJECTS, TOO.

In Pascal, variables are explicitly declared as to its scope and data type before they are used in a program, a procedure or a function. In Ruby, however, variables are objects, that is, they are nothing more than data and methods to manipulate that data.

Let us examine this code fragment again.

def hi (name = "World")
puts "Hello, #{name.capitalize}"
end

In the function hi, the parameter name becomes optional rather than required as a default value is supplied if no parameter is passed to the function at the time of invocation.

So what is name.capitalize? An object consisting of a variable called name, which incorporates the capitalize, uppercase, and lowercase string processing methods (or functions if you will) associated with that object (presuming, of course, that name contains a string of characters).

For example, if name = "world" then puts "Hello, #{name.capitalize}" would output

Hello, World

Likewise, puts "Hello, #{name.uppercase}" would output

Hello, WORLD
?

and as we would expect, puts "Hello, #{name.lowercase}" would output

Hello, world

This would be very difficult if not impossible to implement in a traditional language such as Pascal, which was designed for structured programming, rather than object oriented programming. (Delphi and Turbo Pascal 7.0 are variants of Pascal that implement object oriented features, but Pascal itself was not intended to be a OOP language.)

Better yet, try doing this exercise in BASIC!

Technically, Ruby has only objects and methods, and they are created and deleted on demand whereas Pascal has variables, data types, procedures, and functions, all of which must be explicitly declared before they can be used in a program.

If you are looking for data types in Ruby, they do not exist, at least in the traditional sense. Variables do not need to be explicitly defined before they are used.

However, if you wish to "declare" variables (in the tradition of Pascal) in a Ruby program, I recommend documenting these variables and their intended types by placing these declarations in a comments section of the source code. The data types used by Ruby are classified as follows:

Strings: Variables created and used here consist of zero or more characters enclosed in quotes, or simply entered from a keyboard, a disk file, or other input stream. Anything that makes up a string in the C, C++ and Java languages is also a string in Ruby. This includes the "/n" (for new line, or ASCII code 0x0a), "/r" (for the return carriage, or ASCII code 0x0d), and "/t" (for tabulate, or ASCII code 0x09).

Numbers: Ruby supports integer as well as floating point numbers, just as with any traditional language.


INPUT AND OUTPUT STATEMENTS

In our Hello World example, we used the puts function to output objects. Normally, this statement outputs to what we call stdout in C and C++, or the standard output device, usually the screen or terminal window. From the command line, this can be redirected to a disk file, a network (TCP/IP or UDP/IP) port, a UNIX pipe, or a print queue (through the lpr command).

Just as puts produces output from objects, we can also allow Ruby to ask for input of data to objects. The gets function does just that. There are some differences on what parameters gets supports.

First, a variable object is required as a parameter.

Second, puts allows placeholders to be included within quoted strings when accompanied by their corresponding variable objects. One would think that including such a string would display the quoted string as a prompt before asking for input. But, this is not the case.

As gets function was intended for input of data to variable objects, it simply would not make sense to assign data input to quoted strings, including those that include placeholders.

So, how do we get a prompt on the screen for input? We could use puts, but puts automatically places the cursor at the beginning of the next line on the screen before asking for input. (For the PCLinuxOS implementation, this is done with a return carriage character.)

Ruby implements the print function that does the same thing as puts with one exception, i.e. the cursor is kept on the same line as the object that was just output. So, how would this work in our Hello World program?

We could have written the Hello World program as follows:

#!/usr/bin/ruby
puts "Hello World"

...and we would have been done with it. (This particular example happens to be the world's shortest "Hello World" program ever, with Ruby taking the honors for this implementation.)

Having said that, we could implement a way to allow the input of a name, then use that name to output the greeting.

#!/usr/bin/ruby

print "Please type in your name "
name = gets
puts "Hello, #{name}"

As gets is a function, called without parameters, it will always return a string object.


WE GOT YOUR NUMBER

In this example, if you type in a number, gets returns that number as a string containing the characters that represent the number.

For instance, if you type 1048576, gets assigns "1048576" to the object name. This object can be converted to a floating point number and assigned to another variable object.

#!/usr/bin/ruby
print "Please type in your name "
name = gets
number = name.to_f
puts "Hello, #{name}"

The method .to_f converts the contents of the variable object from which .to_f was called ( in this case, name ) from a string of characters to a floating point number. If the conversion is successful, the floating point number 1048576.0 should be assigned to number.

If the conversion of that same number is not successful, then number would be assigned a floating point value of 0.0 (i.e. zero point zero, or the grade point average of Bluto Blutowsky in the movie Animal House.)

Of course, we would not know whether this was successful at this moment until we output number using either print or puts.

Suppose we wanted to enter an integer instead of a floating point number. The following code would accomplish that:

#!/usr/bin/ruby
print "Please type in your name "
name = gets
number = name.to_i
puts "Hello, #{name}"

As we can guess, .to_i converts string objects to integers the same as .to_f converts strings to floating point numbers.

Placeholders used in the print and puts statements work the same for floating point numbers and integers as it does for string objects, so we can then write the following statement:

puts "You have typed in the number #{number}"

In place of or appended to the previous puts statement. Let us incorporate this into our hello world program, and save this as numbertest.rb.

#!/usr/bin/ruby

print "Please type in a number "
name = gets
number = name.to_f
puts "You have typed in the number #{number}"

Now, let us execute this twice. Once with a real number, and once with random garbage.

[patrick@localhost ruby]$ ruby numbertest.rb
Please type in a number 1048576
You have typed in the number 1048576.0
[patrick@localhost ruby]$ ruby numbertest.rb
Please type in a number lame duck
You have typed in the number 0.0
[patrick@localhost ruby]$

The first run of numbertext.rb came out as we expected. But, look what happened when we entered "lame duck" for a number. Ruby was able to tell that "lame duck" is not a number and assigned the value 0.0 to number for output.

If we were to implement this program in a traditional language such as Pascal, Fortran, C, or even Python, entering this type of input where a floating point or integer is expected would have resulted in a runtime error and the program would have terminated at the point where the input was asked.

NUMERIC AND LOGICAL OPERATORS

As with any programming language, there are numeric operators (in addition to the functions supplied with Ruby's Math module)

Exponents: When we enter a number such as 28 into a LibreOffice Calc spreadsheet, we would type in 2^8. In Ruby, that same expression is typed in as 2**8, in both cases, the result would be 256.

Unary logical operators: In mathematics, when we want to express the negation of a logical expression or variable, such as not X, we would write !X. Ruby implements the not logical operator the same way we write it in a logical equation. If we wanted to find the logical complement of X, we would type ~X.

Likewise, the unary operators + and - work in Ruby the same way we type them out in mathematical equations on paper or by pressing the +/- key on a calculator, with one exception: when used as a method, + and - are typed in the Ruby code as +@ and -@ respectively.

Basic mathematical operators: The standard symbols we use to code mathematical equations for addition (+), subtraction (-), multiplication (*), and division (/) work the same in Ruby as it does in most other languages and spreadsheets. The modulus (%) works the same way in Ruby as it does in C and C++. (This makes sense as Ruby was written in C to begin with.)

Bit shifting operators: Those of you who are familiar with C and C++, should be familiar with the << and >> operators. For those of you who are not, the << and >> operators take the number to the left of the operator and shift the bits in that number left (<<) or right (>>) the number of bits specified to the right of the operator.

Think of (x << y) as (x * (2**y)), and (x >> y) as (x / (2**y)).

Bitwise logical operators: The symbol "&" performs a bitwise AND on two numbers that are supplied to this operator. For instance ( 0xFF & 0x08 ) will yield 8, which also happens to be the hexidecimal number 0x08.

Likewise, the symbol "|" performs a logical OR on two numbers that are supplied to this operator.

One would think that the "^" operator would represent "to the power of" in an equation, especially when entering exponents on a LibreCalc spreadsheet. But this is not the case. The "^" operator performs a Exclusive OR on two numbers that are supplied to this operator.

(Because of this, the "**" is defined to mean "to the power of" instead of "^", which could cause confusion to programmers and machines alike.)


What is the difference between OR and Exclusive OR?

The logical operator OR will return true if one or both parameters in the equation is true, and false if none of the parameters in the equation is true.

However, the logical operator Exclusive OR (sometimes written as XOR) will return true if one or the other parameter in the equation is true, but not both. XOR will return false if both parameters are true or both parameters are false.

To show it another way:

( true | true ) = true
( true | false ) = true
( false | true ) = true
( false | false ) = false

( true ^ true ) = false
( true ^ false ) = true
( false ^ true ) = true
( false ^ false ) = false

Comparison operators: In Ruby, the comparison operators work as expected, namely:

<=, <, >= and >

Logical operators: The "&&" and "||" represent the AND and OR operators repectfully, but they perform logical comparisons instead of bitwise operations on numbers. Also, these operators are used for non-number object comparisons as well.

The "==" stands for "is equal to", or "is the same as" when it comes to logical comparisons. Likewise, "!=" stands for "not equal to" or "not the same as" in a similar manner.


STRING MANIPULATION WITH NUMERIC OPERATORS

When does 5 x 3 = 555? When the 5 is in quotes. The "+" and "*" operators normally used in mathematical equations also apply to string manipulation in Ruby.

The "+" operator, when used for string manipulation, concatenates two (or more) strings. For example, we can assign a variable object name as follows:

name = "PC" + "Linux" + "OS"

When this statement is executed by the Ruby interpreter, name will contain the value "PCLinuxOS" (I would hope so)

Likewise, the "*" operator does the following:

name =  * 

For example,

puts 5 * 3

would give us a value of 15, whereas

puts "5" * 3

Would give us a value of 555. In this instance, the number 5 is treated as a character which happens to be "5", hence the value returned by "*" as a string manipulator is the original "5" appended by two copies of "5".

If we has written

puts "5" * 3

as

puts 5 * "3",

then puts would have displayed 33333.

However, if we had written

puts "5" * 3

as

puts "5" * "3",

we would have gotten an error message indicating that one of those parameters must be an integer (or otherwise be able to be converted to an integer) for the "*" operator to work for string manipulation.

On the other hand,

puts "5" + "3"

would result in the display of 53 on the screen.


IF/THEN CONTROL STATEMENTS

Let us revisit the numbertest.rb program.

#!/usr/bin/ruby

print "Please type in a number "
name = gets
number = name.to_f
puts "You have typed in the number #{number}"

When I typed "lame duck" where it said "Please type in a number", the program responded with "You have typed in the number 0.0".

The same program would respond with "You have typed in the number 0.0" if I had actually typed in either "0" or with "0.0", and as it is, there would be no way to distinguish between "0" and random garbage, as both would return the number zero ("0.0").

There is an easy fix for this problem.

Like any well designed programming language, Ruby comes with statements that control the flow of the program. The easy fix here is to insert a if/then/else statement that tests the input we entered to see if we really typed in zero ("0") or if we typed in random garbage.

In Ruby, if/then/else statements are coded as follows:

if  then

end

We can include code to be executed if the test does not pass.

if  then

else

end

The keyword end here is required to mark the end of the if/then statement.

In our numbertest.rb program, we can code the if/then statement as follows:

#!/usr/bin/ruby

print "Please type in a number "
name = gets
number = name.to_f
if (number != 0.0) then
validnumber = true
else
if (name == "0.0") || (name == "0") then
validnumber = true
else
validnumber = false
end
end
if validnumber then
puts "You have typed in the number #{number}"
else
puts "Sorry, what you typed in is not a valid number"
end

Now, save this file and run the tests again.

[patrick@localhost ruby]$ ruby numbertest.rb
Please type in a number 0.0
You have typed in the number 0.0
[patrick@localhost ruby]$ ruby numbertest.rb
Please type in a number lame duck
Sorry, what you typed in is not a valid number
[patrick@localhost ruby]$ ruby numbertest.rb
Please type in a number 0
You have typed in the number 0.0

What did we do? We created this block of Ruby code to test the input we typed in.

if (number != 0.0) then
validnumber = true
else
if (name == "0.0") || (name == "0") then
validnumber = true
else
validnumber = false
end
end

The variable validnumber, while not explicitly typed, is considered to be a boolean variable, i.e. a variable that can have only true or false for valid values.

In Ruby, the value nil is considered to have the value of false in logical tests. The difference between false and nil is returned from any function where no result is expected, such as a puts statement.

The first thing we checked in this block of code is to see if the number converted from our input is anything other than zero. If it is, then this has to be a valid number as the .to_f and .to_i methods for number conversion return zero if a string that does not make up a number is passed as a parameter.

If/then/else statements can be nested as I have shown you here. This nesting is necessary for the next test to be included in this block of code.

The next test shown here contains a logical OR statement. The number zero can be entered as either "0" or as "0.0". (There are more ways to enter the number zero, but this is sufficient to demonstrate the if/then/else statements.)

If this test passes, then we can say that what we entered is indeed zero, and not random garbage.

What about the other block of code?

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 simple if/then/else statement simply outputs the appropriate message depending on whether we entered a valid number or not.

Let us go back to the other block of code:

if (number != 0.0) then
validnumber = true
else
  if (name == "0.0") || (name == "0") then
    validnumber = true
  else
    validnumber = false
  end
end

This could have been coded another way.

if (number != 0.0) then
validnumber = true
else
  validnumber = false
  if (name == "0.0") || (name == "0") then
    validnumber = true
  end
end

Here, validnumber changes only if we can prove the input to really be a valid number. While both of these achieve the same result, I prefer the former as the logical flow is more obvious than the latter. But then, that is just a matter of opinion and style.


WHAT WE ACHIEVED SO FAR

So far, we have been able to write simple Ruby programs with the use of variables and control structures.

We are getting a feel now for what it is like to program in Ruby. Next time, I shall get more into string manipulation and control structures.



Previous Page              Top              Next Page