This is a journal entry about Ruby and it's kinda long, so move along now if you aren't interested.
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:
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:
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:
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
There. My Ruby proselytizing is done for the day. Don't kill me.
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
def add_string(str)
str =~ /(\d+)\/(\d+)/ or
raise ArgumentError "Can't add '" + str + "' to " +
self.to_str + ": Badly formed string"
return Fraction.new($1.to_i, $2.to_i)
end
def add_fixnum(n)
return self + Fraction.new(n.to_i, 1)
end
end
class Fixnum
def add_fraction(n)
n.add_fixnum(self)
end
end
class String
def add_fraction(n)
n.add_string(self)
end
end
class Object
def add_fraction(n)
raise ArgumentError, "Can't add a " + self.class.to_s + " to a Fraction"
end
end
Re:Hmmm
djberg96 on 2003-07-24T15:31:25
Hmm..probably. Perhaps I was being too faithful to Dave's code (someone else on IRC already said something similar). In Ruby, as in Perl, TMTOWTDI.
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).
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.