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 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
#include
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 by Renaud Waldura.
Permission to make digital or hard copies of part or all of this work for personal
or classroom use is granted without fee, provided that copies are not made or distributed
for profit or commercial advantage, and that copies bear this notice and full citation
on the first page.
Copyright for components of this work owned by others than Renaud Waldura
must be honored. Abstracting with credit is permitted. To copy otherwise, to
republish, to post on servers, or to redistribute to lists, requires prior
specific permission and/or fee. Request permission to publish from
renaud@waldura.com.
Last modified: 2007/10/08 06:58:41 $
Copyright © 2000-2007 Renaud Waldura
<renaud@waldura.com>