Overloading

I'm a C++ programmer learning Ruby. I would like to make a class that
extracts patterns from a file, and returns an array of values. I
have:

class Extracter
  def initialize(pattern, location)
    @pattern = pattern
    @loc = location
  end
  def extract(stream)
    pats = Array.new
    stream.each_line do |line|
      if line =~ @pattern then
        pats << $~[@loc]
      end
    end
    pats
  end
end

cppFile = File.new("foo.cpp")
incEx = Extracter.new(/#include\s*[<"](.*)[>"]/, 1)
includesArr = incEx.extract(ccpFile)

So far, so good. If not, let me know what I've done wrong. Next, I
want to extend the Extracter class to hold an array of patterns and
locations, or a Proc parser that will return the interesting value
(this is for lines that are tougher than regexp). How do I create a
constructor that will take different types and create the appropriate
arrays internally? The options I see are:

1) Call .class.to_s on each parameter to match the type:
        "pattern.class.to_s =~ /Array/ then # I have an array of regexp
2) Use a non-trvial data structure to map symbols to types
3) Use different functions to fill out my extracter:
        def add_array
        def add_parser

What am I missing? Am I doing this entirely wrong for Ruby?

-Ben

You can do this:

if pattern.class == Array # then ...

Or this:

case pattern
  when Array
    # handle array
  when Regexp
    # handle Regexp
end

Or use this to ensure that pattern is always an Array:

pattern = [pattern] unless pattern.class == Array

-Ed

···

On Mon, Sep 26, 2005 at 10:38:16AM +0900, Ben wrote:

The options I see are:

1) Call .class.to_s on each parameter to match the type:
        "pattern.class.to_s =~ /Array/ then # I have an array of regexp

I'm a C++ programmer learning Ruby. I would like to make a
class that
extracts patterns from a file, and returns an array of
values. I
have:

class Extracter
  def initialize(pattern, location)
    @pattern = pattern
    @loc = location
  end
  def extract(stream)
    pats = Array.new
    stream.each_line do |line|
      if line =~ @pattern then
        pats << $~[@loc]
      end
    end
    pats
  end
end

cppFile = File.new("foo.cpp")
incEx = Extracter.new(/#include\s*[<"](.*)[>"]/, 1)
includesArr = incEx.extract(ccpFile)

So far, so good. If not, let me know what I've done wrong.
Next, I
want to extend the Extracter class to hold an array of
patterns and
locations, or a Proc parser that will return the interesting
value
(this is for lines that are tougher than regexp). How do I
create a
constructor that will take different types and create the
appropriate
arrays internally? The options I see are:

1) Call .class.to_s on each parameter to match the type:
        "pattern.class.to_s =~ /Array/ then # I have an array
of regexp
2) Use a non-trvial data structure to map symbols to types
3) Use different functions to fill out my extracter:
        def add_array
        def add_parser

#3 is the best solution for ruby and is the ruby way. What's
the downside? The deeper you get into ruby, the more you'll
find out that overloading methods based on type dillutes
flexibility, offers no advantage, and gives ugly/less readable
code. Overloading methods based on other things (number of
args, flags, etc) is definitely better, but still has some of
the same disadvantages. I try to avoid overloading based on
type at almost all costs and sometime overload based on other
criteria.

···

--- Ben <benbelly@gmail.com> wrote:

What am I missing? Am I doing this entirely wrong for Ruby?

-Ben

__________________________________
Yahoo! Mail - PC Magazine Editors' Choice 2005

Ben wrote:

I'm a C++ programmer learning Ruby. I would like to make a class that
extracts patterns from a file, and returns an array of values. I
have:

class Extracter
  def initialize(pattern, location)
    @pattern = pattern
    @loc = location
  end
  def extract(stream)
    pats = Array.new
    stream.each_line do |line|
      if line =~ @pattern then
        pats << $~[@loc]
      end
    end
    pats
  end
end

cppFile = File.new("foo.cpp")
incEx = Extracter.new(/#include\s*[<"](.*)[>"]/, 1)
includesArr = incEx.extract(ccpFile)

So far, so good. If not, let me know what I've done wrong. Next, I
want to extend the Extracter class to hold an array of patterns and
locations, or a Proc parser that will return the interesting value
(this is for lines that are tougher than regexp). How do I create a
constructor that will take different types and create the appropriate
arrays internally? The options I see are:

1) Call .class.to_s on each parameter to match the type:
        "pattern.class.to_s =~ /Array/ then # I have an array of
regexp 2) Use a non-trvial data structure to map symbols to types
3) Use different functions to fill out my extracter:
        def add_array
        def add_parser

What am I missing? Am I doing this entirely wrong for Ruby?

General stuff about method overloading and C++ like constructs:

In your case I'd probably do it differently. Maybe something like this:

def extract(io)
  result =
  io.each_line do |line|
    dat = yield line and result << dat
  end
  result
end

includes = File.open("foo.cpp") do |io|
  extract io {|line| /#include\s*[<"](.*)[>"]/ =~ line and $1 }
end

Btw, your regexp might be too greedy because you use ".*". Alternatives
that might work:

/#include\s*[<"](.*?)[>"]/
/#include\s*[<"]([^>"]*)[>"]/

Kind regards

    robert

I wasn't aware that I could compare .class with a type name. I guess
I should have realized that. Thanks!

-Ben

···

On 9/25/05, Edward Faulkner <ef@alum.mit.edu> wrote:

You can do this:

if pattern.class == Array # then ...

Edward Faulkner ha scritto:

The options I see are:

1) Call .class.to_s on each parameter to match the type:
       "pattern.class.to_s =~ /Array/ then # I have an array of regexp

You can do this:

if pattern.class == Array # then ...

maybe just
  if pattern.is_a? Array

Or this:

case pattern
  when Array
    # handle array
  when Regexp
    # handle Regexp
end

or, use the case-equality method directly:
  if pattern === Array

Anyway, I think it would be better to have two different methods.
Given that ruby does not have multiple dispatch, if you ever want to add another option (i.e. build from an Enumerable) if you hardwire type checks you'll have to change the method code.
This is especially bad in the face of subclassing, since you actually have to copy/paste the code.

If you, instead, have different methods for the options, you class would stay open for changes.

···

On Mon, Sep 26, 2005 at 10:38:16AM +0900, Ben wrote:

Edward Faulkner wrote:

The options I see are:

1) Call .class.to_s on each parameter to match the type:
       "pattern.class.to_s =~ /Array/ then # I have an array of regexp

You can do this:

if pattern.class == Array # then ...

Better is:

if pattern.kind_of?(Array)
    ...
end

because someone might want to subclass arrays and still be able to pass them to your code. If you demand only unmodified array classes, you're making the code unnecessarily inflexible.

Even better would be to test for the specific methods you need using object.responds_to?(selector), i.e. to use duck typing. For example, you might need an enumerable object, in which case you'd check if pattern.responds_to?(each). Then I could pass in a SQL query response that happened to be enumerable and return patterns when each element was coerced via #to_s...

mathew

···

On Mon, Sep 26, 2005 at 10:38:16AM +0900, Ben wrote:

--
<URL:http://www.pobox.com/~meta/&gt;
          WE HAVE TACOS

"Array" isn't a type name, strictly speaking. It's a constant that
refers to the class "Array". And classes are objects, so you can
manipulate them directly. For example:

Array.class
=> Class

Array.ancestors
=> [Array, Enumerable, Object, Kernel]

regards,
Ed

···

On Mon, Sep 26, 2005 at 10:51:26AM +0900, Ben wrote:

I wasn't aware that I could compare .class with a type name. I guess
I should have realized that. Thanks!

You mean "if Array === pattern".

-austin

···

On 9/26/05, gabriele renzi <surrender_it@-remove-yahoo.it> wrote:

or, use the case-equality method directly:
  if pattern === Array

--
Austin Ziegler * halostatue@gmail.com
               * Alternate: austin@halostatue.ca

Austin Ziegler ha scritto:

···

On 9/26/05, gabriele renzi <surrender_it@-remove-yahoo.it> wrote:

or, use the case-equality method directly:
if pattern === Array

You mean "if Array === pattern".

oops, obviously you're correct