LINUX GAZETTE
[ Prev ][ Table of Contents ][ Front Page ][ Talkback ][ FAQ ][ Next ]

"Linux Gazette...making Linux just a little more fun!"


Programming in Ruby, part 1

By Hiran Ramankutty


Introduction

Ruby is an interpreted, pure Object Oriented language designed by Yukihiro Matsumoto of Japan, where it is reported to be more popular than Python and Perl! The first part of this series is meant to be a tutorial introduction, with more advanced stuff in the pipeline.

Of course, I need not go through the ritual of advocating the `advantages of Ruby compared to languages X, Y and Z' - most people realize that each language has a unique flavour and character of its own - whether you choose Python or Ruby for your next open source project depends more on the peculiar affinity which you as an individual feel for one over the other, and the availability of standard library facilities, rather than on arcane technical issues. So let's enjoy that unique Ruby flavour!

Requirements

I presume that your development environment is Linux and you have Ruby installed on it. Ruby is free software, and there are no restrictions on it's usage. You can get it from the Ruby Home Page .

Hello World

Let's start with the mandatory `Hello, World'.


% cat > hello.rb
print "Hello World\n"
^D
% ruby hello.rb
Hello World
%

Variables

Categorization is done on the basis of the first character of the name of the identifier:


                $                       global variable
                @                       instance variable
                a-z or '_'              local variable
                A-Z                     constant

The two `pseudo-variables' are exceptions to the above stated rule. They are `self' and `nil'.

Both are named as if they are local variables, but they are not! We will see their real meaning later on.

Global variables

A global variable has it's name starting with $. As such, it can be referred to from anywhere in the program. It is to be noted that a global variable assumes the value 'nil' before initialization. You can test this out:


 % ruby
 print $foo,"\n"
 $foo = 5
 print $foo,"\n"
 ^D
 %

The interpreter responds

nil
5

It is possible for us to `bind' procedures to global variables, the procedures being automatically invoked when the variable is changed. More about this later!

Some special kinds of global variables formed with a single character following a '$' sign are, as a collection, interpreted by Ruby as major system variables (made read-only). Some of them are given below along with their meanings.

Local variables

A local variable has it's name starting with a lower case letter or an '_'. Unlike globals and instance variables, they do not assume the value 'nil', but they behave as shown below:


% ruby
print foo
^D

You will get an error message:
      "undefined local variable or method 'foo' for #(object...)".

The scope of a local variable is confined to one of

If we initialize a local variable in any block(or a procedure), then it remains undefined after exiting from the loop. For example:

def foo(n)
	k = 2 * n
	print "\n",k,"\n"
	print defined? k,"\n"
end

foo 3
print defined? k,"\n"
^D

The output is:
  

6
local-variable
nil

In the above example `defined?' is an operator which checks whether it's argument is defined or not. The results "local-variable" and "nil" (to indicate false) must make it clear.

Constants

Any name with characters following an uppercase letter is treated as a constant. But Ruby programmers, to avoid confusion, use names with all uppercase letters. So 'Foo' as well as 'FOO' are constants. As in case of local variables, a constant is defined by a substitution and an access to an undefined constant or alteration of a defined constant causes an error. Check for yourself.

Strings

Strings in ruby can be single quoted('...') or double quoted("..."). But both are different. Use double quotes if the string contains backslash-escaped characters. Also results of evaluation are embedded for contained expressions quoted by #{}. See examples:

print "\n"
print '\n'
print "\001","\n"
print '\001',"\n"
print "abcd #{5*3} efg","\n"
var = " abc "
print "1234#{var}567","\n"
^D

\n
\001
abcd 15 efg
1234abc567

We will learn more about strings in the next section, arrays. This is to include the features that are similar and are held by both arrays and strings.

Arrays

Arrays can be quoted using '[]'. One of the features of Ruby is that arrays are heterogenous.


a = [1,2,"3"]
print a,"\n"
^D

Now, if you write a Ruby program to add all the elements of the array shown in the above program, you will get an error:

         Error!!String cannot be coerced into Fixnum
The `3' in the array is stored as a string. Now, if it is done like this:

a = [1,2,"3"]
b = a[0] + a[1] + a[2].to_i
print b,"\n"
^D

The program will be executed without any errors. The attachment to a[2] i.e. '.to_i' is to convert the content of a[2] to an integer.You can also try '.to_s'.

Operations like concatenation and repetition can be done on arrays.


a = [1,2,"3"]
print a + ["foo","bar"]
print a * 2
^D

We get

123foobar
123123

It's possible to `slice and dice' arrays. Here are some examples:


a = [1,2,"3","foo","bar"]
print a[0],"\n"
print a[0,2],"\n"
print a[0..3],"\n"
print a[-2..2],"\n"
print a[-3..-1],"\n"

Arrays and strings are inter-convertible. An array can be converted to a string with 'join', and a string is split up into an array with 'split'.


a = [1,2,3]
print a[2],"\n"
a = a.join(":")
print a[2],"\n"
print a,"\n"
a = a.split(":")
print a[2],"\n"
print a,"\n"
^D

The Associative Array is another important data structure - it's also called a `hash' or a `dictionary'. It's basically a name-value mapping, as shown below:


h = {1 => 2, "2" => "4"}
print hash,"\n"
print hash[1],"\n"
print hash["2"],"\n"
print hash[5],"\n"
^D

I hope the results are convincing!

Control structures

If - else

Let us write the factorial function.The mathematical definition is:


      n! = 1			(when n==0)
      n! = n * (n-1)!		(otherwise)
      
In ruby this can be written as:

def fact(n)
	if n == 0
		1
	else
		n * fact(n-1)
	end
end
print fact 4,"\n"

You get 24.

Ruby has been called `Algol like' because of the repeated occurrence of `end'. In this recursive call, you may notice the lack of the return statement. In fact, use of return is permissible but unnecessary because a ruby function returns the last evaluated expression (does this sound a wee bit Lispish? If you insist, you sure can do Lisp in Ruby!)

The for loop


for i in 0..4
	body-of-for
end

Here i is the variable and 0..4 is the range.In the case of strings, you can very well write:


for i in "abc"

The while loop

Try this out

i=0
while i < 10
	print i+=1,"\n"
end

Case

We use the case statement to test a sequence of conditions. Try this out.

i = 7
case i
when 1,2..5
	print "i in 2 to 5\n"
when 6..10
	print "i in 6 to 10\n"
end
^D

You get

i in 6 to 10

2..5 means the range including 2 and 5. It checks whether i falls within that range.

This can be applied to strings as shown below.

case 'abcdef'
when 'aaa','bbb'
	print 'contains aaa or bbb \n"
when /def/
	print "contains def \n"
end
^D
contains def

Note the slash used with "def". It is used for quoting a regular expression. We shall see it later.

Modifications with control structures

The case statement mentioned just above actually tests for the range (i in 2..5) as

(2..5) === i 

The relationship operator '===' is used by case to check for several conditions at a time. '===' is interpreted by ruby suitably for the object that appears in the when condition.

Thereby in the example with strings equality is tested with first when and an expression is matched with the second when.

Now try using the operator '===' with if structure(try implementing functions like isalnum(),isalpha(),isnum() etc.).

Your code can be shortened when we have to use if and while constructs for individual statements: as shown below

i = 7
print "contained in 5..10\n" if (5..10) === i
print i-=1,"\n" while i > 0
^D
contained in 5..10
6
5
4
3
2
1
0

You may at times want to negate the test conditions. An unless is a negated if, and an until is a negated while. This is left up to you to experiment with.

There are four ways to interrupt the processing of statements of a loop from inside. First, as in C, break means, to escape from the loop entirely. Second, next skips to beginning of the next iteration of the loop (corresponds to continue statement in C). Third ruby has redo, which restarts the current iteration. The following is C code illustrating the meanings of break, next, and redo:

while(condition) {
  label_redo:
	goto label_next;		/* ruby's "next" */
	goto label_break;		/* ruby's "break"*/
	goto label_redo;		/* ruby's "redo" */
	...
	...
  label_next:
}
label_break:
...

The return statement is actually the fourth way to get out of a loop from inside. In fact return causes escape not only from the loop but also from the method that contains the loop.

Conclusion

We have examined some elementary language features - enough to get you started with a bit of `quick-and-dirty' coding. As I learn more about this `gem' of a language, I shall be sharing my experience with you through future articles. Good bye!

Hiran Ramankutty

I am a final year student of Computer Science at Government Engineering College, Trichur. Apart from Linux, I enjoy learning Physics.


Copyright © 2002, Hiran Ramankutty.
Copying license http://www.linuxgazette.net/copying.html
Published in Issue 81 of Linux Gazette, August 2002

[ Prev ][ Table of Contents ][ Front Page ][ Talkback ][ FAQ ][ Next ]