ALINK="#FF0000"> [ Prev ][ Table of Contents ][ Front Page ][ Talkback ][ FAQ ][ Next ]
LINUX GAZETTE
...making Linux just a little more fun!
Programming in Ruby - Part 3
By Hiran Ramankutty

Review

In part 1 we looked at the basic syntactic structure of Ruby. In part 2 we discussed iterators and the fundamentals of Object-Oriented Programming. Here in part 3 we explore object-orientedness in more detail.

Methods

A method is an action the object knows how to perform on request. Let's see an example of how a method is invoked for an object:

print "asdfgh".length
^D
6

One can infer from this that a method named `length' of the String object is called.

Now try this:

foo = "abc"
print foo.length,"\n"
foo = [1, 2]
print foo.length,"\n"
^D
3
2

From the result it is clear that deciding which method to call is done at execution time, and that the choice differs depending on the content of the variable.

I suggest that readers not bother about how the object determines its length because the process is different for strings and arrays. Fortunately, Ruby automatically chooses the correct process, so we don't have to worry about it. This feature in languages supporting object orientedness is called polymorphism.

It is not necessary for the user to know how the methods are processed, but one has to know what methods are acceptable to the object. When an object receives an unknown method, an error is raised. For example: try calling the "length" method for the object "foo" with value "5".

I had mentioned about a special variable self in Ruby. It is the object which calls methods. Such callings are used very often and so an abbreviation is available. That is;

self.method_name(arguments...)

can be omitted then

method_name(arguments...)

causes same effect. Called function is just an abbreviation for method calling to self.

Classes

The real world consists of objects which can be classified. For example, a one-year-old child may think `bowwow' on seeing a dog or even a fox. In terms of object orientedness, `bowwow' can be termed class, and an object belonging to a class is called instance.

In Ruby, as in other object oriented languages, we first define a class to determine the behaviour of the object, and then make an instance of the class, a specific object. So let's define a class in Ruby.

class Dog
	def bark
		print "Bow wow\n"
	end
end
^D

The definition of the class lies between the keywords `class' and `end'. A `def' in class syntax defines a method of the class.

Now that we have a class named `Dog', let's make an object.

tommy = Dog.new
tommy.bark
^D
Bow wow

This makes a new instance of the class Dog and substitutes it into the variable tommy. The method `new' of any class makes a new instance. Now the variable tommy has properties defined in the class Dog, and so he can `bark'.

Inheritence

Ever wondered how others classify objects? One example is how people perceive a dog. A mathematician may see a dog as an object made up of different numbers and figures, a physicist may see it as the result of many natural and artificial forces at work, and my sister (a zoologist) may interpret it as a representative of the species canine domesticus. To her, a dog is a kind of canine, a canine is a kind of mammal, and a mammal is always an animal.

Hence we see that the classification of objects takes the form of a hierarchy, though not in all cases. Let's see what Ruby does with it.

class Animal
	def breath
		print "inhales and breaths out\n"
	end
end
class Cat<Animal
	def bark
		print "mew\n"
	end
end
tama = Cat.new
tama.breath
tama.bark
^D
inhales and breaths out
mew

Here the Cat class isn't given any definitions on how to breathe, but it will inherit that property from the Animal class. In this case, the `bark' feature is just appended.

It is notable that the properties of the parent class or the super class is not always inherited. For example, birds fly, but penguins don't. Penguins do have other properties of birds, though, like laying eggs. This kind of thing can be represented in Ruby also, and I leave it to the reader as home work.

To make a new class using inheritence from a superclass that holds common properties, we only need define the differences from the superclass. Some say this `differential programming' is one of the merits of object oriented programming.

Redefining Methods

We can observe difference in behaviour of the instances in subclasses when we redefine the superclass methods. See below:

class Human
	def print_id
		print "I'm a human.\n"
	end
	def train_toll(age)
		print "reduced-fare.\n" if age < 12
	end
end
Human.new.print_id
class Student1<Human
	def print_id
		print "I'm a student.\n"
	end
end
Student1.new.print_id
class Student2<Human
	def print_id
		super
		print "I'm a student too.\n"
	end
end
Student2.new.print_id
^D
I'm a human.
I'm a student.
I'm a human.
I'm a student too.

In the new classes that redefine the superclass methods, the original method of superclass can be called using `super'. Along with the code above, try this:

class Student3<Human
	def train_toll(age)
		super(11) # unconditionally reduced
	end
end
Student3.new.train_toll(25)
^D
reduced-fare.

These are simple examples, but I hope they give you and idea of how inheritance and redefinition works.

More on Methods

There are some methods which play the role of restricting the way a method is called. For a function (defined at top level) given below:

def sqr(x)
	x * x
end
print sqr(5)
^D
25
When `def' appears outside of class definition, it has effect of adding this method to the Object class. The Object class is the base class of all other classes- all classes inherit from the class Object. The means that the method `sqr' can be used in all other classes.

Now that all classes must be able to call `sqr', let's try to call `sqr' to `self':

print self.sqr(5)
^D
ERR: private method `sqr' called for (Object)

Calling the function using `self' after the definition of the function gives the error as shown above. The error message is not intuitive, so what does it mean?

What is happening is that a method that is defined at the top levelcan can be called using function style as opposed to method style. See what error message you get when undefined methods are called.

Since methods are called in a function type style, it works in a fashion similar to that of C++, while calls are within the class or its subclass.

We can restrict access to methods using `public' or `private' - public methods can be called by users of the class, while private methods can only be called by other methods inside this class.

class Test
	def foo
		print "foo\n"
	end
	private foo:
	def bar
		print "bar -< "
	foo
	end
end
temp = Test.new
temp.foo
temp.bar
^D
ERR: private method `foo' called for (Test)
bar -< foo

The concept must be clear with the kind of output obtained.

Singleton Method

The behaviour of an instance is determined by the class, but we know that a particular instance should have special behavior. In most languages, we must make another class for the instance, while in Ruby we can append methods to a paticular instance without much fuss.

class SingletonTest
        def size
                print "25\n"
        end
end
t1=SingletonTest.new
t2=SingletonTest.new
def t2.size
        print "10\n"
end
t1.size
t2.size
^D
25
10

Here t1 and t2 belong to the same class, though, t2 redefines the `size' method so it will behave differently. A method of a particular instance is called singleton method.

One example where singleton methods are used is in the buttons of a GUI program, where each button has a different action. We can redefine the action suitably for each button object.

Modules

Modules in Ruby are very similar to classes, but are used to group related classes together. There are three major differences between modules and classes:

  1. Modules have no instance.
  2. Modules have no subclass.
  3. Modules are defined by module ... end.

Roughly saying, there are two uses of modules. First, to collect methods or constants. For example:

print Math::PI,"\n"
print Math.sqrt(2),"\n"
^D
3.141592654
1.414213562

The operator `::' refers to a constants in a module or class. When we refer directly to the methods or the constants of a method, we use the `include' method.

include Math
print sqrt(2),"\n"
print PI,"\n"
^D
1.414213562
3.141592654

Another use of modules is called `mixin'. This can be complex so should be explained in detail.

In some Object-Oriented programming languages, a class can inherit from more than one superclass; this feature is called multiple-inheritance. Ruby purposely doesn't have this feature. Instead, we can make it by mixin with the module.

As said above, the module works like the class; the methods or the constants of a module cannot be inherited, but instead are appended to other modules or classes by use of include. So, when one includes the definition of a module, this adds the property (mixes the property) into the class.

mixin modules appear in the standard library, and by mixing in these modules to a class whose the `each' method returns each element, the class get the features: `sort', `find', etc.

The following differences exist between multiple-inheritance and mixin:

These differences inhibit complex relationships between classes; simplicity is a good thing. This is why Ruby doesn't have multiple inheritance. In languages that have multiple inheritance, situations can occur where classes have many superclasses and the relationship of instances form a tangled network... Situations like this are too complex to understand for the brain of the human being, or at least my brain...

On the other hand, mixins make it simple as just `the collection of particular properties all we want to add'.

So, even in a language with multiple inheritance, it is recognized that it is good to extend classes by using mixin rather than developing complicated inheritance relationships. We advanced this idea in Ruby allowing mixins only instead of multiple inheritance.

Procedure Objects

Suppose you are writing a program that does something to process signals. Those familiar with it will understand the simplicity in sending a procedure as an argument to a method (here usually arrival of signals).

The built-in method proc generates a procedure object. The procedure code goes between braces, and to execute the procedure object one uses the call method. See below:

obj = proc{print "Hello world\n"}
obj.call
^D
Hello world

C programmers will find procedure objects similar to function pointers.

Conclusion

With this, we come to an end of the series of articles Part 1, Part 2 and Part 3 with which I have intended to give readers a basic introduction to programming in Ruby. I have not tried to present hard core programming in Ruby: this is my final year of Engineering, and I am busy with my final year project and have been unable to look deeply into Ruby. But I know that as time permits, I will come up with much more in Ruby.

Happy Programming...

 

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


Copyright © 2003, Hiran Ramankutty. Copying license http://www.linuxgazette.net/copying.html
Published in Issue 86 of Linux Gazette, January 2003

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