banner
Previous Page
PCLinuxOS Magazine
PCLinuxOS
Article List
Disclaimer
Next Page

The Ruby Programming Language: Introduction


by phorneker


Over the years, I have worked in some way with traditional languages such as Pascal, Fortran, C, C++, Java, Forth, Prolog, LISP, Smalltalk, assembly language for 8 and 16-bit processors, Ada, REXX (from my OS/2 days) and yes, even BASIC. In short, I have explored just about any programming language that was ever created.

Yes, I have had some exposure to Perl, Python, Erlang and Falcon (all of which are available in the PCLinuxOS repository). But, the one language I really want to learn is Ruby.


What is Ruby?

Ruby was created in 1995 by Japanese computer scientist and programmer Yukihiro Matsumoto and could be described as a object oriented general purpose scripting language with a philosophy, namely so that programmers can be productive (and have fun doing it). In Matsumoto's words, the philosophy is described as follows (from Wikipedia):

I was talking with my colleague about the possibility of an object-oriented scripting language. I knew Perl (Perl4, not Perl5), but I didn't like it really, because it had the smell of a toy language (it still has). The object-oriented language seemed very promising. I knew Python then. But I didn't like it, because I didn't think it was a true object-oriented language -- OO features appeared to be add-on to the language. As a language maniac and OO fan for 15 years, I really wanted a genuine object-oriented, easy-to-use scripting language. I looked for but couldn't find one. So I decided to make it.

...and he did indeed make it. The current version in the PCLinuxOS repository is 2.6.1 at the time of this writing and version 3.0 is under development.

Trivia Note: The language Ruby was named for the birthstone of one of Matsumoto's colleagues, according to FAQ 1.1.3 on the Ruby Documentation website.

The original release of Ruby was developed on this Sony workstation running NEWS-OS, which was based on BSD 4.3.



This particular machine ran on two 68030 processors, not unlike the processors used in classic Macintosh (running Mac OS 7.x and earlier), Atari ST/STE/TT/Falcon series, Sharp X68000 series, and most Commodore Amiga machines of that day.

One thing these machines were great at is desktop publishing and graphics, given the 1280x1024x256 color resolution of the displays, outdone only by the SGI (Silicon Graphics) machines of that same era, which cost thousands more than this machine did.

(Imagine producing this magazine on one of these machines!)

More than just a programming language interpreter

Ruby comes with its own package manager. Like Python, TeXLive, and Perl, Ruby's libraries are maintained in packages. The package manager, called rubygems, is used to install and maintain the Ruby installation the same way we maintain PCLinuxOS itself, and its public repository has a URL of https://rubygems.org/gems.

Source code files written in Ruby are saved with the .rb extension. Ruby packages are created and maintained with a file extension of .gem .

The .gem extension is also used in the OpenGEM product for its configuration files.

The GEM product here refers to Digital Research's product from the 1980s that supplied a graphical interface to CP/M and was intended to compete with Windows and Mac OS (classic) systems, which themselves were attempts to reimplement the Xerox Alto.

Fortunately for us, Xerox never enforced its patent on the graphical user interface. Otherwise, much of the technology (hardware and software) we use today, would never have come into existence.

GEM had been implemented for DOS, Atari ST series, Apple IIgs, CP/M, and Commodore 64/128. Today, GEM itself is now free software under the GPL, but there is no need for the original since we now have OpenGEM as a replacement.

Ruby, similar to many other language interpreters, can be run in interactive mode by opening a terminal window and typing irb (for Interactive Ruby).



Like most everything else in Linux command line interpreters, typing exit will get you out of this interpreter and back to the command prompt.

There are numerous tutorials available online for you to check out. I used the Ruby in Twenty Minutes tutorial at https://www.ruby-lang.org/en/documentation/quickstart/ to learn the language. It starts out with using irb for the famous "Hello World" program, and then using irb as a glorified calculator.

For this introduction to Ruby, let us start with two simple tasks we can do inside irb. This interpreter can be used as a glorified calculator, or as a replacement for the bc command line calculator. (For this demonstration, I exited out of irb, and launched bc.)



For those of you not familiar with the bc command, this is what happens when you launch this rudimentary command line calculator. Notice that there is no prompt, so if you are not used to running command lines in a UNIX terminal, you would think the system has stopped.

In fact, bc is waiting for you to type something in. Even in 1991, one would think that having some kind of prompt to tell you that it is ready for your next command would be central to any successful user interaction.

In the tutorial I used, a simple addition equation, namely "5 + 5" was entered into irb to get the desired result (which should be "10"). Let us try this in bc.



As we can see, there is nothing to distinguish what was typed from the result I got except that the result appears on another line, and then only because I pressed the return key.

Exiting from bc is not intuitive. Pressing Control-Z, when exiting from the DOS command cp con: <textfile>, gracefully exits from the copy command (cp in DOS). It turns out that pressing Control-Z here gives me this:



...which is what I would expect when pressing Control-C in most command line operations. It turns out that pressing Control-D gracefully exits bc, which in my opinion should have appeared somewhere in the banner when bc was launched.

Now, let us try the same thing in irb.



As we would expect, we got the correct result. Note that the result generated by Ruby is preceded by a right arrow (=>) and a space, making the result readable and distinguishable from what was typed.

What Ruby did was evaluate the equation "5 + 5" directly from the command line.

Now, look at the prompt.

Before I typed "5 + 5", the prompt displayed "irb(main):001:0".

After the expression was evaluated, the prompt now displays "irb(main):002:0".

This is normally used to keep track of how many commands or statements were executed, not unlike what could be found in a LISP interpreter (i.e. the clisp package in the PCLinuxOS repository).

As I stated earlier in this article, the exit command gracefully exits irb as would be expected of any well implemented language interpreter (including bash), hence making this a glorified calculator, and hence would eliminate the need for bc.

Mathematical expressions are not the only type of expressions Ruby can evaluate. As Ruby was written in C, any expression that can be evaluated in C is also evaluated in Ruby...and this brings up to the famous "Hello World" program.

From what I have shown you so far, there are two ways to get "Hello World" to display in Ruby.

We could simply type "Hello World" in irb (quotes included) as shown here:



...of which irb simply evaluates the string as it is. Ruby implements the puts command, for "put string", to do the same thing that I did just now. When I type puts "Hello World", in irb, we get the following:

?

...and look what happened. As we would expect, "Hello World" was displayed. But, there is one notable difference, namely the "=> nil" on the next line. Normally, puts expects one or more variables to be passed as parameters. The "Hello World" here could have been replaced with any arbitrary text enclosed in quotes. Since puts had no variables passed as parameters, the nil result was shown indicating that nothing additional was available to output, other than the "Hello World".

As with C and C++, everything in Ruby is either a function or an expression. As puts is considered to be a function, it has to return some kind of value. The nil result means that there is no value to be returned and hence will always be the result of executing puts. If you were to assign the result of puts to a variable, that variable will always be assigned a nil value.

The equivalent in C (and C++) is printf("%s", "Hello World");, which evaluates to the same thing as the Ruby expression puts "Hello World".


Ruby is a general purpose calculator

When I typed in "5 + 5" in irb, we got 10. The spaces inside the equation are optional and are shown here for readability.



While both are readable in this (partial) screenshot, it is clear which statement is more readable on the screen.

As we would expect, the "+, -, * and / " function as we would expect. When it comes to powers and exponents, such as 28, Ruby implements exponents as 2**8, rather than 2^8 as it would be typed in a 1-2-3, Quattro Pro or LibreOffice Calc spreadsheet.

Square roots, however, are implemented in Ruby differently. On a spreadsheet, we would type =sqrt(1048576) to get 1024. The equivalent in Pascal is var := sqrt(1048576); , and the equivalent in C is var = sqrt(1048576); .

In Ruby, however, we would type Math.sqrt(1048576), and voila:



we get the result we need, but as a floating point number. Why Math.sqrt instead of just sqrt?

Earlier I mentioned that Ruby installations are maintained the same as PCLinuxOS itself with the rubygems package manager. Ruby modules are what is generally contained inside Gems. Some modules such as Math are packaged with the stock Ruby installation, and it is possible for sqrt to be implemented in a third party module. Therefore the Math.sqrt statement tells Ruby to use the sqrt function from the Math module.

One of Ruby's influences is Smalltalk. In Smalltalk, interaction between modules is by way of message passing, a concept that should be very familiar to those who have programmed applications for Windows, the X Window System, OpenGEM and classic Mac OS environments. The concept is commonly referred to as event driven programming.

What happens in this example was that the sqrt function contained in the Math module is passed a message, that is a message containing the number 1048576 as a parameter of which the sqrt is instructed to return the square root of 1048576, which should be 1024.

Note, the result is always a floating point number. If sqrt were to be implemented as an integer function, the result would not be reliable enough for everyday use as what would be returned would not be accurate. For example, the square root of 2 is 1.4142135623730951 as implemented in Ruby.



Many cheap physical calculators do not get this much precision in the result, but if this were to be implemented as an Integer function, Math.sqrt(2) would simply return "1", and that would not be very usable, or correct for that matter.

Let us try the square root of zero. As we would expect, we get 0.0, as it should be.

Now, can this function handle imaginary numbers? Those of us with a mathematical background know that the square root of -1 is i (for imaginary).

Can Ruby's Math module handle this function?



As we can see here, support for imaginary numbers has not been implemented in Ruby, yet..at least not with the Math module. Also, we see how Ruby handles exceptions with no exception handling module in use.

Therefore, the Math.sqrt function requires a number that is greater than or equal to zero for a parameter.

There is a Method to this Madness

As we saw, the square root function in the Math module does not support imaginary numbers. The code used to implement the sqrt function is considered to be a method, or a set of instructions that implement a function in one way.

Suppose someone were to call a similar function that supports imaginary numbers, and then decides to call this sqrt but with different parameters. The code contained in this sqrt function is another method for implementing the square root function.

According to Wikipedia,

A method in object-oriented programming (OOP) is a procedure associated with a message and an object. An object is mostly made up of data and behavior, which form the interface that an object presents to the outside world. Data is represented as properties of the object and behavior as methods.

This definition comes close to what I just described. Perhaps, in a future release of Ruby, imaginary support could be implemented in the Math module by including a (built in) data type that supports imaginary numbers, and then adding new code (the method) for each function where imaginary numbers are the parameter(s) used.

As Ruby is an object oriented language, the additional code to the Math module will not change existing code for programs that call functions from the Math module. In fact, existing programs that used the Math module will not notice the difference. However, programs that use a imaginary number data type will need the new version of the Math module to function properly.

Now that we have an idea on what methods are, let us see what we can do in irb.

If you have ever programmed anything in Pascal, what we call a method here is nothing more than a Procedure. This is true even in object oriented implementations of Pascal such as Delphi (supported in FreePascal, or the fpc package in the PCLinuxOS repository).

Defining Methods in Interactive Ruby

We can indeed define methods using irb. Let us write our "Hello World" program as a method.

Each method in Ruby starts with def, meaning "define function" or simply "define" depending on how you look at it. This is followed by the name of the method.



Look what happens in the irb prompt. Ruby took the def statement and is now storing anything we type in irb from this moment until we type end on a separate line into memory allocated for the method called hi.

The "001:0" changed to "002:1". The line counter increments with each instruction, but note that the ":1" indicates that this is a method (or procedure) being defined rather than an instruction to be immediately executed.

Next, we retype the puts "Hello World" statement.



Again, Ruby took in the statement, and is waiting for the next line in the method. Since this is a "Hello World" program, all we need to do is place the end statement in a separate line.



We have now successfully defined a new method and Ruby has acknowledged the method by typing a " => :hi " for a response. We have just added a new function to the Ruby interpreter, namely hi. Unlike the built in functions, this new function will be effective as long as the Ruby interpreter is running in this session.

Type hi next to see what we have done.



Where have we seen this before? When we typed puts "Hello World" on a line by itself before we defined this function.

As with any object oriented language, the name of the function can have more than one method associated with the function. Which method is used depends on the parameters passed to the function.

The first hi function contains no parameters.

def hi
puts "Hello World"
end

Let us redefine the hi function with one parameter.

def hi(name)
puts "Hello #{name}"
end

Let us try out this method of hi. The name you type in must be in quotes or Ruby will give you an error message.



When parameters are properly supplied, we get this:



But, what is this #{name}? This tells Ruby to use the variable name within the {} instead of printing out "name". If this looks like something from a Perl script, it is because Perl is one of the influences for the Ruby language design.

Before this def statement was entered, I had to restart the Ruby interpreter. The current version of Ruby does not allow the method name to be used more than once in a session. When I tried to call hi without any parameters, Ruby spat out an error message to that effect.

Let us examine the existing code.

def hi(name)
puts "Hello #{name}"
end

What if there was a way to set a default value if no name is supplied for a parameter? There is a way to do that with what we have. Just place a default value inside the parameter in the def statement as shown (in italics):

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

So what else can we do to improve the code? Make sure the name is properly capitalized. Ruby contains a function for that, and you simply need to append .capitalize to the variable name. (Remember, everything in Ruby is an object.)

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

Now, let's try this code out.



We can see that everything worked out so far. When no parameter was supplied, Ruby set World to the variable name.

Creating Objects

As Ruby is an object oriented language, and that everything in Ruby is an object, it only makes sense that we should be able to create objects. As in C++ and Java, we can create class objects that encapsulate what we have learned already. Let us revisit our existing code.

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

How can this be encapsulated into an object? By defining a class. We start with a class statement. The tutorial I used calls this class "Greeter", and it is only appropriate that I use the same name for the class being defined.

class Greeter

Classes contain methods and their data. In a class, the data contained in that class is defined first usually initialized to a set value for each variable. This is important as variables that are undefined can contain arbitrary values at the time objects of this class are created, and if those undefined values are inappropriate for the data being represented, such as strings where floating point numbers are expected, what could happen would be nonsensical output at best, in other words, what could happen would be unpredictable.

Having said that, we need to create a variable that is local to the class.

class Greeter
def initialize(name = "World")
  @name = name
end

The end statements are similar to the end statements in several languages (including Pascal) in the sense that they end fragments of code. In this case, the end statement here ends the declaration for the initialize method, which should be the first method defined in any class as that gets executed first with the creation of class variables.

Now, let us add two methods to this class (and close out the class with an end statement).

class Greeter
def initialize(name = "World")
  @name = name
 end
def say_hi
  puts "Hello #{@name}"
 end
def say_bye
  puts "Bye, #{@name}, come back soon"
 end
end

After typing all of that in, we get:



Ruby responds with a "=> :say_bye" (the name of the last method defined in the Greeter class). We have just defined a new class. Unlike methods, classes are not directly executed in irb. Instead, new objects of that class have to be defined.

We accomplish this by typing in the following:

greeter = Greeter.new()

where <name> is the name you want to use enclosed in quotes. Let us use the same example.



Now, we have an object named greeter, derived from the Greeter class. We can now invoke each of the two methods we defined in the class.



...and everything works as we expected.

We've just barely scratched the surface. This gives you an idea of how object oriented languages work. As with any programming language, there is much more to cover. Ruby is billed as an easy to learn language, and should be familiar to those of us who programmed in C, C++, Java, Python, PERL, and Smalltalk. The philosophy behind this is indicative of the hard work and dedication Matsumoto put into creating and maintaining Ruby, and I am looking forward to working more with this language.

What I just showed you was the Interactive Ruby interpreter, and the ways methods and classes are written in Ruby, and a little exposure to handling of variables. 5From what I have read so far in the documentation, I can see infinite possibilities for what could be done with this language,

Next time, I will show you how to create Ruby source files and introduce you to some other basics of the programming language such as control structures, data types, and arrays.



Previous Page              Top              Next Page