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.
12 Responses to “Ruby For Java (and C#) Programmers, Part 3 – Introducing Arrays, Hashes and the typing system”
Nice piece. Keep it going. I’m going through dave’s ruby book(2nd ed) and it’ll be nice how this continues.
TYPO
====
“…..stufy the sights and more importantly the sounds of the wildlife around him…..”
you meant “study” the sights…. ?
BR,
~A
this is good stuff sam!
how’s about some io comparison next time. -dealing with network, files and such.
aslak
Aslak – I do plan to look at API comparisons (especially IO and XML handling) although this might be in part 5. In the next part I’m either going to look at the dynamic nature of Ruby’s typing, or look at more advanced use of Hashes/Arrays and some string handling.
Oh, and thanks for stopping the typo Anjan – I really shouls stop writing this stuff late at night…
If you’re taking votes, I vote for “the dynamic nature of Ruby’s typing” for the next article.
That’s certainly coming up Gary – although next I think I’m either going to look at string and IO handling, or I’ll be looking at map/reduce.
How do Java’s new “Generics” (like C++ Templates) solve (or not) your comparison on the Duck / Dog speak method call. I’m reckoning they do.
I’m not sure how they do solve the above example (and there are differences between Java generics and C++ templates) – the initial problem here is that @Dog@ and @Dog@ are in seperate hierarchies. The two Java solutions are either to implement a more fine-grained interface (e.g. @Communicative@) which defines the @speak@ method, or to define a new base interface as I showed above. With Ruby there is no need for either. Don’t forget as well that Ruby’s type system is also dynamic – the lack of a dynamic typing system is the reason why you end up having to mess around with code generation, bytecode weaving and dynamic proxies for things like AOP, JDO and Hibernate in Java.
Um, shouldn’t this:
array.find_all { | item | item => 8 } –> [8, 10]
be this?
array.find_all { | item | item >= 8 } –> [8, 10]
Yes Chirs, it certainly should 🙂 In an alternative universe, I’m sure there is a me who actually proof reads this stuff and runs the code snippets through irb…
Call me Mr Picky if you want (it’s an improvement on “Chirs” at least), but I also think you mean “Naturalist”.
Godamit! Corrections being made now….grr…
Nice article, except:
@array.each { | item | puts item.to_s }@
Because:
@array.each { | item | puts item }@
and:
@puts array@
Would do *exactly* the same thing. Maybe:
@array.each{|item| puts item.length}@
But that could be:
@puts array.map{|item| item.length}@
In Ruby world, you’d write something as simple as possible, and that means as concise as possible most times.
And it’s often a good idea to separate the IO from the “real” code.
Really good article though. I’d like to read more about Enumerable methods, because this slightly functional module is hard to understand for Java/C# programmers. (C# is going functional too, with anonymous functions)
Keep up the good work!