Specifically, this is a response to Dave Cross' article "Overloading" on perl.com, a problem I have with Dave's code and how Ruby is just better at OO. You've been warned twice now. Hopefully I won't get kicked off of use.perl or anything...
My first *major* issue is with the fact that Dave returns undef in the event of a failure when creating a new Fraction object (except in one case, where it croaks). Why not croak on the spot for all failures? Dave's approach could cause great confusion, forcing me to track down "can't call method on 'undef'" error messages later on. I won't be able to tell right away if the constructor failed or if the Fraction object was undef'd somewhere later on accidentally. Why not at least give me the chance to wrap the constructor in an eval so I can trap that error if I'm so inclinded? As it is I'll probably be stepping into the debugger at some point, or I'll be forced to write extra tests to account for this possibility. Blech.
Ok, on to a Ruby version of the code. Let's start with the base class:
# Note that the '@' can be replaced with 'self' if you prefer
# normalise() method skipped since it wasn't actually provided in the article
class Fraction
attr_reader:num, :den
def initialize(num=nil,den=nil)
if num && den
unless num.kind_of?(Fixnum) && den.kind_of?(Fixnum)
raise TypeError, "two-argument form accepts Fixnum's only"
end
@num = num
@den = den
elsif num && den.nil?
unless num.kind_of?(Fraction)
raise TypeError, "one-argument form accepts Fraction type only"
end
@num = num.num
@den = num.den
else
@num = 0
@den = 1
end
end
end
First, this just *looks* cleaner, mkay? Specifically, I'm using actual parameter names in the constructor instead of $_[0] and $_[1], plus the ->{x}, "ref", and UNIVERSAL::isa syntax is an eyesore IMO. In addition, it's slightly less code.
Second, while I can't declare a type for the parameters (hey, this ain't Java, ok? - but see below), I can give them a name and a default value. This provides an extra advantage over the Perl equivalent in that I can never pass more than two arguments without causing an error.
Third, because everything in Ruby is an object, and all objects have a type, we can use the kind_of? method to test the actual type of each argument rather than resorting to regular expressions, which is clunky. It works in Dave's code, but in general I think that approach is prone to error.
On to the overloading. Dave provides an "add" method, then overloads the "+" operator. Here's the Ruby equivalent:
def +(n)
if n.kind_of?(Fraction)
num = (@num * n.den) + (n.num * @den)
den = @den * n.den
return Fraction.new(num,den)
elsif n.kind_of?(Fixnum) # Single digit
return self + Fraction.new(n,1)
elsif n =~/(\d+)\/(\d+)/
return self + Fraction.new($1.to_i,$2.to_i)
else
raise ArgumentError, "Can't add a " + n.class.to_s + " to a Fraction"
end
end
alias add +
No special keywords required and about half the code. Just define the "+" method as you see fit. This also has the added advantage of causing an error if you try to pass more than one argument, while Dave's version silently ignores extra arguments contained within @_. To be fair, though, almost no one checks the argument length to methods in Perl (including me) because it's so rarely an issue.
While Ruby doesn't include any built-in mechanism for overloading, there *is* a package on the RAA called "overload" (go figure) written by Nobu Nakada that allows you to achieve the same effect. Consider:
# Java weenies take note
require "overload"
class Fraction
def initialize
#...
end
overload(:initialize)
def initialize(x)
#...
end
overload(:initialize,Fraction)
def initialize(x,y)
#...
end
overload(:initialize,Fixnum,Fixnum)
end
# Now I can do this...
a = Fraction.new
b = Fraction.new(a)
c = Fraction.new(1,2)
#... and Ruby will call the appropriate constructor
The best of all worlds.
There. My Ruby proselytizing is done for the day. Don't kill me.
Hmmm (Score:2, Insightful)
class Fraction
def +(n)
return n.add_fraction(self)
end
def add_fraction(n)
return Fraction.new((@num * n.den) + (n.num * @den),
@den * n.den)
end
de
Re:Hmmm (Score:3, Insightful)
Returning "undef" vs croaking (Score:3, Insightful)
Actually, returning "undef" from the constructor is a vital part of the way the module works when it comes to constant overloading. It's the one instance of using "croak" that is a bug.
Reply to This