2008-08-12

class Class

Today, let's have a look at classes in Ruby. You can get basic information about these animals under the links on the right, here I'm going to clarify some more unclear areas, like:
- Constructors,
- Static methods,
- Methods overloading,
- Fields.

Constructors
How to make a new object of a class? Well, in most old-school languages there are constructors. They are some sort of a hybrid methods: they are called as if they were static methods, but inside them, it was just like inside a normal methods - you can access object fields and call other its methods. In Ruby the two parts - static and nonstatic - are separated. In fact, you make a new object of class Klass, like this:
Klass::new
or this:
Klass.new
. It doesn't resemble classic constructor calling which would be more like
new Klass
. In fact, what you do in Ruby, you call a static method
new
. This method is the method of each class as it is the method of the class
Class
. And this
Class::new
does some magic: it creates a new object, allocates memory for it, makes it be this or that class, and in general performs some action that is not possible anywhere else in Ruby, only in
Class::new
.

Once the object is constructed, the magical method wants to initialise the object, so that it can get some specific attributes (until now, this object's only attribute is its class). To do this, it calls object's method
initialize
, which is a perfectly regular instance method - not static, not even a bit. So that explains why you declare the method
initialize
and then call
new
to create the object. The arguments you pass to
new
are completely ignored inside
Class::new
, they are only sort of forwarded to object's initialiser. (Later you'll learn how to do such a forwarding.)

A small example to test what I just said:
class K
def initialize(a,b,c)
puts "a=%d and b=%s and c=%02X"%[a,b,c]
end
end

K::new(131,"quale",63)

Static methods
Prepare for a shock now: there are no static methods in Ruby. Yes, you read correctly. What I was calling a static method (and I'm going to use this naming convention normally because it makes things easier) is in fact an instance method of the class object. Because you must know that all classes, like
String
or
Array
or anything, are in fact objects (instances) of the class
Class
. You can check it:
5.class #=> Fixnum
"abc".class #=> String
Array.class #=> Class
So, for instance, Array is an object of the class Class. So when we write
Integer.induced_from(8.3)
, it might look like a static method of the class
Integer
, but it is not quite it. That's why you cannot say
10.induced_from(8.3)
, even though
10.is_a? Integer
, so you could call a static method in this way in languages like C++ or Java. (
10.is_a? Integer
this is how you check if an object is an instance of the given class;
10.class==Integer
wouldn't work because
10.class
is in fact
Fixnum
and
Fixnum
is an ancestor of
Integer
).

But OK, if it's getting too hard for you, just remember that what seems to be static methods, is not quite the same as static methods in other languages. But it is similar.

Just a code sample
class K
def self.static_method1
puts "in static 1"
end
class << self
def static_method2
puts "in static 2"
end
end
end

def K.static_method3
puts "in static 3"
end

K.static_method1
K.static_method2
K.static_method3
One day I'll explain why methods can be declared in so many ways. For now just remember that they are all alike.

Methods overloading
Today's a negative day: there's no method overloading in Ruby. If you define a method, and then define another method with the same name, the last one is the one that will really be accessible. There's no way to call the first one. Try it yourself, change a method in one of the standard classes:
class String
def length(q)
puts q
end
end

"OneTwoThree".length(456)
As you see, the newly created method
length
was called. There is no way to call the old one:
"aString".length
yields an error now.

So how can you create a method that can receive either String or Array or possibly Integer? Well, Ruby is a dynamically typed language so of course if you just define the method, it can receive arguments of any type. But if you want to serve them differently, then you must use a condition and check the class of the passed object:
def test_class(arg)
if arg.is_a? String
puts "Called with String."
elsif arg.is_a? Array
puts "Called with Array."
elsif arg.is_a? Integer
puts "Called with Integer."
else
puts "Called with something else."
end
end
So that's how you can emulate overloading. But the truth is that we can simplify a lot the last example:
def test_class(arg)
puts "Called with %s."%arg.class
end
Of course the behaviour is different (e.g. you'll get
Fixnum
instead of Integer for numbers), but that's more ruby'ish, and the function looks nicer, too.

Another trick to emulate method overloading is default parameters, like here:
def def_par(a,b=6,c=a+b)
puts c
end

def_par(3) #=> 9
def_par(3,9) #=> 12
def_par(3,9,2) #=> 2
As you see, the default values can be variable, and even very variable as
c
here.

One more trick about overloading: let's say we want to receive a list (Array) of Strings into our method, and print them, like here:
def str(list)
list.each{|s| puts s}
end
But this has a little inconvenience - if you happen to pass just one String to the method, you must write
str(["string"])
. So we'd like to be able to pass one String, or Array of Strings. We could of course check the class of the argument but let's move on and learn something else:
def str(list)
Array(list).each{|s| puts s}
end
The method
Array
(it's a method here, not a class) returns an array containing one element - the argument, or returns just the argument if the argument is already an Array. So that's exactly what we needed.

Last one example, without a deep explanation for now:
def str(*list)
list.each{|s| puts s}
end

str("one")
str("one","two","three")
str(*["one","two","three"])

Fields
It is not elegant to declare public fields in classes. In Ruby it is also not possible. Fields in classes look like this:
@field
. They can be accessed only from within the object, they cannot even be accessed from inside other object of the same class (what distinguishes Ruby from C++ or Java). So how to make them visible? You define setter and getter. You can do it like this:
class K

def initialize(a)
@a=a
end

# this is the getter
def a
@a
end

# this is the setter, isn't that the most elegant syntax for a setter?
def a=(v)
@a=v
end

end

k=K::new(56)
puts k.a #=> 56
k.a="33"
puts k.a #=> "33"
Of course you can write some more interesting code, like value validation and so on, especially in the setter. But if this trivial setter/getter is what you need, you can use an abbreviation:
class K

#...

attr_accessor :a,:b # trivial setter and getter for @a and @b
attr_reader :c # trivial getter and no setter for @c
attr_writer :d # just the setter (well, I never used this)

end
Don't look at these colons in this way, they won't bite. We'll get to Symbols later, for now just remember how to define the attribute accessors in trivial and in nontrivial way.

One last remark about fields: they are always defined. If you run a new irb console and write
xx
, it's going to say that the variable or method is not defined. But if you do this:
class KK
def test_xx
puts @xx
end
end

KK::new.test_xx
you'll just see
nil
. Class attributes (that's the more ruby-like name for fields) are never undefined. They are just
nil
.

All for today, thanks for listening.

No comments: