Stories
Slash Boxes
Comments
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

djberg96 (2603)

djberg96
  (email not shown publicly)

Journal of djberg96 (2603)

Wednesday July 23, 2003
05:08 PM

Ruby response to perl.com article on overloading

[ #13650 ]
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:

# 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.

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
 Full
 Abbreviated
 Hidden
More | Login | Reply
Loading... please wait.
  • Hmmm (Score:2, Insightful)

    Surely:


    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)

      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.