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 endclass 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.