A Comparative Look At Some Ruby Idioms

In this article we look at some well-known programming patterns and compare them to their equivalent written in the Ruby language. The goal of this article is not to determine whether Ruby is a better language than any other, merely to put into perspective its particuliarities, i.e. the Ruby Way of writing common tasks. This article does not claim to be an exhaustive presentation of Ruby's features.

Constructors

Constructors are required for

class MyCounter { MyCounter(int i) { _i = i; } private int _i; // instance variable } MyCounter m = new MyCounter(9);
Constructors in Java
class MyCounter def initialize(i) @i = i end end m = MyCounter.new(9)
Constructors in Ruby

Accessors

Accessors are methods used to manipulate instance variables. They are a very important part of a good encapsulation strategy.

class MyCounter { private int counter; int getCounter() { return counter; } void setCounter(int n) { counter = n; } } MyCounter m = new MyCounter(); m.setCounter(9); int n = m.getCounter();
Accessors in Java
class MyCounter def counter @counter end def counter=(n) @counter = n end end m = MyCounter.new m.counter = 9 n = m.counter
Accessors in Ruby
class MyCounter attr_accessor :counter end m = MyCounter.new m.counter = 9 n = m.counter
Shortcut accessors in Ruby

The snippets above show that in Ruby an object can be used without knowing whether a given member is really an instance variable or a method call -- this is known as the Uniform Access Principle and is one of the points put forward by the Eiffel language. Is it a profound feature decoupling a class interface from its implementation or merely syntactic sugar? You judge. At the very least it saves on typing more parentheses.

Function Pointers

"Function pointer" is really a C/C++ idiom. I use it to describe the action of passing a block of code to another part of the program for later execution.

class MyClass { public: int myMethod(int); } int MyClass::myMethod(int i, int j) { return i + j; } // take a pointer fp to myMethod() int (MyClass::*fp)(int, int) = &MyClass::myMethod; // instanciate a MyClass MyClass m = new MyClass(); // call myMethod() through the function pointer int n = (m.*fp)(1, 2); // -> 3
Pointer to member function in C++
sub f { $_[0] + $_[1] } # take a reference to the function f $fr = \&f; # call the function through the reference $n = &$fr(1, 2); # -> 3
Function pointers in Perl
@array = (1, 2, 3, 0); @mapped_array = map { $_ + 1 } @array; # -> (2, 3, 4, 1) @sorted_array = sort { $b <=> $a } @array; # -> (3, 2, 1, 0) @grepped_array = grep { $_ > 1 } @array; # -> (2, 3)
Perl blocks with map/sort/grep
p = proc { |i, j| i + j } p.call(1, 2)
Function pointers in Ruby
class MyClass def myMethod(i, j) i + j end end # instanciate a MyClass m = MyClass.new # get the symbol representing myMethod inside the MyClass m fp = m.method( :myMethod ) # call the method through the pointer fp fp.call(1, 2) # -> 3
Method pointers in Ruby

Method pointers are created in Ruby with Object#method, that is, the method named method of the Object class -- confusing isn't it?

Iteration and Iterators

The act of going through some elements in a data structure.

char[] data = { 'a', 'b', 'c', 'd', 'e', 'f' }; for (int i = 0; i < data.length; i++) { System.out.println( data[i] ); }
Iteration in Java, old school
Character[] data = { 'a', 'b', 'c', 'd', 'e', 'f' }; List<Character> ldata = Arrays.asList(data); for (char c : ldata) // or "data", works on arrays and collections { System.out.println( c ); }
New style of iteration in Java (JDK 1.5)
@data = 'a'..'f'; foreach $item (@data) { print "$item\n"; }
Generic iteration in Perl
map { print "$_\n" } @data;
Iteration in Perl with map
data = 'a'..'f' data.each { |c| print c, "\n" }
Iteration in Ruby

Iterators are first-class citizens in the Ruby language, and made extensive use of. They permeate the language and are an integral part of programming in Ruby. Most traditionnal "for" loops can be replaced by calls to iterators.

Variable Argument List

Variadic functions/methods are called with an undefined number of arguments.

#include <stdarg.h> #include <stdio.h> void print_nums(int count, ...) { va_list ap; int i; va_start(ap, count); /* Initialize the argument list. */ for (i = 0; i < count; i++) { int num = va_arg(ap, int); /* Get the next argument value. */ printf("%d\n", num); } va_end(ap); /* Clean up. */ } main() { print_nums(3, 102, 5, 689); }
ANSI C (this example adapted from the GNU C manual)
def print_nums(*numbers) end
Ruby

Exceptions

Exceptions are used to deal with error conditions in an "out of band" manner, ie. outside of the main control flow.

FileInputStream f = null; try { f = new FileInputStream("z.txt"); // and read from the file ... // an error condition was found throw new IOException("Oops; we goofed"); } catch (FileNotFoundException fnfe) { System.out.println("The file was not found!"); } catch (IOException ioe) { throw ioe; // rethrow } finally { if (f != null) f.close(); }
Exception handling in Java
eval # try { open(F, "z.txt") or die "$! when opening z.txt"; # read from F ... # found error die "Oops; we goofed"; }; if ($@ =~ /when opening/) # catch { warn "An error occurred: $@"; } elsif ($@) # catch all { die; # rethrow } close F;
Exception handling in Perl
begin f = File.open("z.txt") # do something with f # ... # detected an error condition raise "Oops, we goofed" rescue Errno::ENOENT print "The file was not found: ", $!, "\n" rescue # catch all raise $! # rethrow ensure f.close if f end
Exception handling in Ruby

The handling of exceptions is quite similar in the 3 languages; Ruby offers a nice balance between totally typed exceptions (Java) and totally untyped exceptions (Perl): an exception can be only caught by type, but it can be raised untyped.

NOTE: the true Ruby way to process a file is probably to use an iterator, e.g.

File.open("file.ext") { |f| f.readlines.each { |l| print l # for example } }
Processing a file line-by-line in Ruby (this example from the Ruby FAQ)

True and False

Threads and Synchronization

Namespace Control

Modules

Multiple Inheritance

Among languages used in this article, only C++ offers true multiple inheritance, and ways to solve the associated problems (i.e. the diamond-shaped inheritance graph). Java simulates quite effectively multiple inheritance with interfaces, abstract classes with no data members. Ruby takes a different approach and uses modules to implement mix-ins that simulate multiple inheritance.

Ruby's modules ...

Regular Expressions

Java offers regular expressions (regexps) through external packages only; a popular one is OROMatcher by Daniel Savarese. Ruby's regexps are an integral part of the language and modeled after Perl's.

# Example 1 $data = "Perl Programming"; if ($data =~ /^P/ || $data =~ /ming$/) { $data =~ s/[pm]/X/gi; # substitute all occurrences, case-insensitively print "$data\n"; # -> Xerl XrograXXing } # Example 2 $data = ""; while ($data =~ /()/ig) { ; }
Perl regular expressions
# Example 1 data = "Programming in Ruby" if data =~ /^P/ || data =~ /ming$/ data.gsub!(/[pr]/i, 'X') # call method gsub!() on String object print "#{data}\n" # -> XXogXamming in Xuby end # Example 2 data = "" data.gsub() { | match | . }
Ruby regular expressions


Copyright © 2000-2007 Renaud Waldura <renaud@waldura.com>