Following on from Part 2, we look at two of Ruby’s most important collection classes, Hashes and Arrays, as well as have a brief look at the typing system.
Update 1: Fixed typo, thanks Anjan – any day now I’ll stop writing this stuff so late at night I’m too tired to proof read.
Update 2: Fixed blatant syntax error – thanks Chris
Arrays
We’ve already seen a little of one of Ruby’s built in types, the array. You can work with them much like Java’s Array:
[ruby]
array = [4, 6.0, “Hello”]
array0 —> 4
array1 —> 6.0
array2 —> “Hello”
array.length—> 3
[/ruby]
Where in Java we’d need a for loop to iteratie through the elements in an array, in Ruby we can use a number of built-in array method each. For example to print out the string value of each item in the array:
[ruby]
array.each { | item | puts item.to_s }
[/ruby]
The array class makes use of the Enumerable module – any class which implements each and @ can include the @Enumerable module, getting a variety of helpful methods for free:
[ruby]
array = [4, 6, 8, 10]
array.find_all { | item | item => 8 } —> [8, 10]
array.include?(6) —> true
array.include?(“Fish”) —> false
array.partition { | item | item [[4, 6], [8, 10]]
array.min —> 4
array.collect { | item | item – 2} —> [2, 4, 6, 8]
[/ruby]
Hashes
Another of Ruby’s collection types is Hash. Like Java’s Hashtable or HashMap classes – a construct which uses a key to index an object. To access the values in a hash, you use the element reference construct, []:
[ruby]
ages = { “sam” => 28, “norman” => 60, “fred” => 45 }
ages[“sam”] —> 28
ages[“fred”] —> 45
ages[“jane”] —> nil
ages[“sam”] = 30
ages[“sam”] —> 30
[/ruby]
Hash also implements Enumerable:
[ruby]
ages = { “sam” => 28, “norman” => 60, “fred” => 45 }
ages.each { | name, age | puts ”#{name} is #{age} years old”}
ages.sort —> [[“fred”, 45], [“norman”, 60], [“sam”, 28]]
ages.include?(“sam”) —> true
ages.include?(“jane”)—> false
ages.partition { | name, age | age [ [[“fred”, 45], [“sam”, 28]], [[“norman”, 60]] ]
[/ruby]
We’ll be looking at more advanced uses of both Arrays and Hashes later on.
“Quack quack” – the Ruby typing system
There is a saying – “If it walks like a duck and talks like a duck, it must be a duck”. In Ruby, you don’t say something is a duck – you make it act like a duck. In Java, you hang a nice big sign around it’s neck saying “duck”, then go about implementing the duck methods. On the face of it, this isn’t a big difference. Compare:
public interface Duck {
void waddle(int distance);
String speak();
}
public class Mallard implements Duck {
public void waddle(int distance) {
...
}
public void speak() {
return "Quack!";
}
}
public class Pintail implements Duck {
public void waddle(int distance) {
...
}
public String speak() {
return "Quack!";
}
}
And in Ruby:
class Mallard
def waddle(distance)
...
end
def speak
return "Quack!"
end
end
class Pintail
def waddle(distance)
...
end
def speak
return "Quack!"
end
end
Now lets imagine a Dog class, who can also speak:
public interface Dog {
String speak();
}
class Dalmation implements Dog {
public String dog() {
return "Woof!";
}
}
And in Ruby:
class Dog
def speak
puts "Woof!"
end
end
Now lets imagine a Naturalist, who’s job it is to study the sights and more importantly the sounds of the wildlife around him. If we just wanted to study ducks, then we could define a simple interface:
public interface Naturalist {
void study(Duck duck);
}
public class WildlifeReporter implements Naturalist {
public void study(Duck duck) {
tapeRecorder.record(duck.speak());
}
}
And in Ruby:
class Naturalist
def study(duck)
tape_recorder.record(duck.speak)
end
end
Now if we wanted our Naturalist to also study a Dog, in Java we’d either have to define two methods – one study that takes a Duck, and another that takes a Dog – but more likely we’d define another interface (say, Animal) from which both Dog and Duck derive, and redefine study to take that new interface. With Ruby, our Naturalist is the same for both – all it cares about is that whatever gets passed into study defines a speak method.
Now implementing the Animal doesn’t seem like a big deal, and in this (highly contrived) example, it isn’t. But you can see how you’ll quickly end up with a number of type definitions, all defining the same operations, just so you can work in a generic fashion with a variety of objects. The typical solution to this in Java is to have many very fine-grained interfaces (in some cases you’ll even see many interfaces only implementing a single method).
We’ll be looking more at the Ruby typing system in later parts.