Getting the right class with inheritance and super()

I'm wondering what the _right_ way is to go about getting the right
class with inheritance and super()

Take the following class I'm using for a graphing tool: Like the name
says, it creates a fixed size array. The << operator allows you to
use it as if it were a circular queue of sorts, and the size= method
allows you to re-size the array if necessary.

The issue is that while << works fine, the results from + and - return
an Array, not a FixedSizeArray, UNLESS I run the returned value
through a FixedSizeArray.new(), and somehow, that just seems wrong and
wasteful.

Am I crazy? Is that really the right way?

class FixedSizeArray<Array
  def initialize(size=1,fill=nil)
    #Sizes under one don't make sense
    if size.is_a?Array
      super(size)
    else
      super(size,fill)
    end
    self
  end

  def size=(newsize)
    unless length==newsize
      if length > newsize
        self.slice!(Range.new(0,length-newsize-1))
      elsif length < newsize
        self.unshift(*Array.new(newsize-length){0})
      end
    end
    size
  end
  
  def <<(data)
    self.shift()
    super(data)
    self
  end

  def -(data)
    #Works, but feels somehow...wasteful and wrong
    FixedSizeArray.new(super(data).unshift(*Array.new(size-super(data).length,nil)))
  end

  def +(data)
    #Works, but feels somehow...wasteful and wrong
    FixedSizeArray.new(super(data).slice(Range.new(data.length,size+data.length)))
  end
  
end

I'm wondering what the _right_ way is to go about getting the right
class with inheritance and super()

Maybe it's just me, but I often use encapsulation, instead.

The issue is that while << works fine, the results from + and - return
an Array, not a FixedSizeArray, UNLESS I run the returned value
through a FixedSizeArray.new(), and somehow, that just seems wrong and
wasteful.

I would guess it's a result of C code making assumptions (Array is Core stuff,
after all, so a lot of C) -- maybe something doing the equivalent of
Array.new, rather than self.class.new.

With an encapsulated pattern, though, you'd at least be avoiding a new Array
object each time:

class FixedSizeArray
  def initialize(size=1,fill=nil)
    # Old duck-typing habit: kind_of, not is_a
    if size.kind_of? Array
      @array = size # Not duplicated -- careful.
    else
      @array = Array.new(size, fill)
    end
    # Why return self here? FixedSizeArray.new will always
    # return the new object. Return value here is ignored.
  end

  def size=(newsize)
    # No need to check length==newsize, really
    if @array.length > newsize
      @array.slice!(Range.new(0,length-newsize-1))
    elsif @array.length < newsize
      @array.unshift(*Array.new(newsize-length){0})
    end
    size
  end

  def <<(data)
    @array.shift
    @array << data
    self
  end

  def -(data)
    self.class.new((@array -
data).unshift(*Array.new(size-super(data).length,nil)))
  end

  def +(data)
    # No need for Range; slice will take two arguments
    self.class.new((@array + data).slice(data.length,@array.size+data.length))
  end

  # Add some array-ness
  include Enumerable
  def each *args, &block
    @array.each *args, &block
  end
end

I have no idea which will be more efficient, though.

Also, does this graphing tool need to be fast? If not -- if I understand what
you're trying to do -- you might consider simply shrinking it down to size
(with a single slice call) when you need it. One line is a lot easier to
write than a whole class.

···

On Wednesday 24 September 2008 15:45:13 Kyle Schmitt wrote:

I'm wondering what the _right_ way is to go about getting the right
class with inheritance and super()

Maybe it's just me, but I often use encapsulation, instead.

Same here. In this particular case you need to ensure *all*
manipulating methods are overridden in a way that they do not break
class invariants (max size for example). Kyle's version for example
did not deal properly with #shift, #unshift, #push, #pop, #concat to
name a few.

Also, since inheritance is a "is a" relationship this could send the
wrong message. Can a FixedSizeArray really be used whenever an Array
is used? I doubt it.

The issue is that while << works fine, the results from + and - return
an Array, not a FixedSizeArray, UNLESS I run the returned value
through a FixedSizeArray.new(), and somehow, that just seems wrong and
wasteful.

I would guess it's a result of C code making assumptions (Array is Core stuff,
after all, so a lot of C) -- maybe something doing the equivalent of
Array.new, rather than self.class.new.

Method #coerce is not implemented in your classes. This has to be
done in order to make + and - work properly across the board. See
here for example

http://c2.com/cgi/wiki/wiki?RubyCoerce

Basically you need to do something like this:

class X
  def coerce other
    # this method needs to return proper results
    # depending on the type of argument
    case other
    when Integer: [other, to_i]
    when Float: [other, to_f]
    else
      raise TypeError, "Cannot coerce #{other.inspect}"
    end
  end

  def + other
    if self.class === other
      # fake only
      self.class.new
    else
      a, b = other.coerce(self)
      a + b
    end
  end

  def - other
    if self.class === other
      # fake only
      self.class.new
    else
      a, b = other.coerce(self)
      a - b
    end
  end

  def to_i
    1
  end

  def to_f
    1.0
  end
end

x = X.new

# uncomment next line to see what happens
# set_trace_func lambda {|*a| p a}

puts x + 10, 20 + x, x + 30.0, 40.0 + x, x + x

With an encapsulated pattern, though, you'd at least be avoiding a new Array
object each time:

# Add some array-ness
include Enumerable
def each *args, &block
   @array.each *args, &block

#each conventionally returns self which does not happen here. In 1.9
you would even go as far as to do this

def each(&b)
  if b
    @array.each(&b)
    self
  else
    to_enum(:each)
  end
end

end
end

I have no idea which will be more efficient, though.

IMHO you could get rid of the shifting in your version by introducing
two position markers.

Also, does this graphing tool need to be fast? If not -- if I understand what
you're trying to do -- you might consider simply shrinking it down to size
(with a single slice call) when you need it. One line is a lot easier to
write than a whole class.

Certainly true!

Kind regards

robert

···

2008/9/25 David Masover <ninja@slaphack.com>:

On Wednesday 24 September 2008 15:45:13 Kyle Schmitt wrote:

--
use.inject do |as, often| as.you_can - without end

Maybe it's just me, but I often use encapsulation, instead.

Humm. It is a good possibility..

class FixedSizeArray
def initialize(size=1,fill=nil)
   # Old duck-typing habit: kind_of, not is_a
   if size.kind_of? Array
     @array = size # Not duplicated -- careful.
   else
     @array = Array.new(size, fill)
   end
   # Why return self here? FixedSizeArray.new will always
   # return the new object. Return value here is ignored.

This was a cleaned out version of the code, there was other cruft
between the end of the if block and the initialize method :slight_smile: that's
the only reason, but it's a very good point.

end

Also, does this graphing tool need to be fast? If not -- if I understand what
you're trying to do -- you might consider simply shrinking it down to size
(with a single slice call) when you need it. One line is a lot easier to
write than a whole class.

The graphing tool just needs to be fast enough to graph in the
incoming data, which means rendering at perhaps 5 frames per second.
Not fast at all. The resizing code isn't meant to be run many times,
I figured if you're using an array of fixed size, you're going to set
the size very very rarely. The important bits are the ability to
append, add and subtract from the array without changing it's size.

Thanks.

--Kyle

···

On Wed, Sep 24, 2008 at 9:09 PM, David Masover <ninja@slaphack.com> wrote:

Maybe it's just me, but I often use encapsulation, instead.

Same here. In this particular case you need to ensure *all*
manipulating methods are overridden in a way that they do not break
class invariants (max size for example). Kyle's version for example
did not deal properly with #shift, #unshift, #push, #pop, #concat to
name a few.

For the moment I was concentrating on the methods I would be currently
using, and filling it in when I had time. Actually that would be good
practice to fill it in.

Also, since inheritance is a "is a" relationship this could send the
wrong message. Can a FixedSizeArray really be used whenever an Array
is used? I doubt it.

I'd like it to. This was a first blush since I needed (more wanted,
but anyway) it in this project.

Method #coerce is not implemented in your classes. This has to be
done in order to make + and - work properly across the board. See
here for example

Thank you, I think coerce may be just what I wanted. Lemmie give this
a quick going over.

IMHO you could get rid of the shifting in your version by introducing
two position markers.

Taking a really quick look at the array.c file, it looks like that's
already what shift does, or did I misunderstand what I read? Being
fast enough for now, I wanted to avoid premature optimization,
especially if it means coding the other methods will be easier :slight_smile:

Thanks!
--Kyle

···

On Thu, Sep 25, 2008 at 2:26 AM, Robert Klemme <shortcutter@googlemail.com> wrote:

2008/9/25 David Masover <ninja@slaphack.com>:

Robert, I'm confused.

Reading the example you gave, and even some on the net, it looks like
I'd need the Array class to have a coerce method that can change an
Array into a FixedSizeArray?

--Kyle

Maybe it's just me, but I often use encapsulation, instead.

Same here. In this particular case you need to ensure *all*
manipulating methods are overridden in a way that they do not break
class invariants (max size for example). Kyle's version for example
did not deal properly with #shift, #unshift, #push, #pop, #concat to
name a few.

For the moment I was concentrating on the methods I would be currently
using, and filling it in when I had time. Actually that would be good
practice to fill it in.

Since I would recommend using delegation instead of inheritance you
don't have to. If you use inheritance you must make sure that your
class's invariant is not broken.

Also, since inheritance is a "is a" relationship this could send the
wrong message. Can a FixedSizeArray really be used whenever an Array
is used? I doubt it.

I'd like it to. This was a first blush since I needed (more wanted,
but anyway) it in this project.

If you think about it for a moment you'll realize that your
FixesSizedArray is far from usable in every place where an Array is
used. Just think about algorithms that rely on collecting arbitrary
many objects in an Array (e.g. File.readlines) - you cannot use your
FixedSizedArray there.

Kind regards

robert

···

2008/9/25 Kyle Schmitt <kyleaschmitt@gmail.com>:

On Thu, Sep 25, 2008 at 2:26 AM, Robert Klemme > <shortcutter@googlemail.com> wrote:

2008/9/25 David Masover <ninja@slaphack.com>:

--
use.inject do |as, often| as.you_can - without end

Since I would recommend using delegation instead of inheritance you
don't have to. If you use inheritance you must make sure that your
class's invariant is not broken.

Upon thinking about it more, and attempting to flush it out in the
inheritence direction, I'm coming to agreement about encapsulate
instead.

I'd like it to. This was a first blush since I needed (more wanted,
but anyway) it in this project.

If you think about it for a moment you'll realize that your
FixesSizedArray is far from usable in every place where an Array is
used. Just think about algorithms that rely on collecting arbitrary
many objects in an Array (e.g. File.readlines) - you cannot use your
FixedSizedArray there.

I agree that it can't be used everywhere, but where I want to use it,
I want it to look and feel very much like an array.
However, the case of File.readlines may be an example of exactly how
I'd like to use it: it gives you a fixed amount of data.
f=FixedSizeArray.new(100)
f<<File.readlines("/var/log/messages")

Then the question becomes does the principle of least surprise. Is it
least surprising to read in the first 100 lines, in this case, and
ignore the rest, or is it least surprising to let the 100 line window
slide to the end, and only deal with the last hundred?

--Kyle

···

On Thu, Sep 25, 2008 at 9:51 AM, Robert Klemme <shortcutter@googlemail.com> wrote:

Reading the example you gave, and even some on the net, it looks like
I'd need the Array class to have a coerce method that can change an
Array into a FixedSizeArray?

No. Your coerce method needs to take care. Look at my example. I
did not change Fixnum or Float.

···

2008/9/25 Kyle Schmitt <kyleaschmitt@gmail.com>:

On Thu, Sep 25, 2008 at 9:51 AM, Robert Klemme > <shortcutter@googlemail.com> wrote:

If you think about it for a moment you'll realize that your
FixesSizedArray is far from usable in every place where an Array is
used. Just think about algorithms that rely on collecting arbitrary
many objects in an Array (e.g. File.readlines) - you cannot use your
FixedSizedArray there.

I agree that it can't be used everywhere, but where I want to use it,
I want it to look and feel very much like an array.

That's ok but that does not mean that a FixedSizedArray *is an* Array.
I was talking about semantics of inheritance which is usually denoted
*is a* relationship.

Cheers

robert

--
use.inject do |as, often| as.you_can - without end

Since I would recommend using delegation instead of inheritance you
don't have to. If you use inheritance you must make sure that your
class's invariant is not broken.

Upon thinking about it more, and attempting to flush it out in the
inheritence direction, I'm coming to agreement about encapsulate
instead.

I'd like it to. This was a first blush since I needed (more wanted,
but anyway) it in this project.

If you think about it for a moment you'll realize that your
FixesSizedArray is far from usable in every place where an Array is
used. Just think about algorithms that rely on collecting arbitrary
many objects in an Array (e.g. File.readlines) - you cannot use your
FixedSizedArray there.

I agree that it can't be used everywhere, but where I want to use it,
I want it to look and feel very much like an array.
However, the case of File.readlines may be an example of exactly how
I'd like to use it: it gives you a fixed amount of data.
f=FixedSizeArray.new(100)
f<<File.readlines("/var/log/messages")

This should be:
f = FixedSizeArray.new(100)
f.concat File.readlines("/var/log/messages").first(100)

First of all you won't end up with an Array with n elements as first
element in your FixedSizeArray, and it also tells the reader which of
the lines will be used (you may want to use #last instead, depending
on your implementation).
I think it would be nice to have a behaviour as if you would #each
over the incoming array and push each element on the fixed size array,
but YMMV.

^ manveru

···

On Fri, Sep 26, 2008 at 12:41 AM, Kyle Schmitt <kyleaschmitt@gmail.com> wrote:

On Thu, Sep 25, 2008 at 9:51 AM, Robert Klemme > <shortcutter@googlemail.com> wrote:

Then the question becomes does the principle of least surprise. Is it
least surprising to read in the first 100 lines, in this case, and
ignore the rest, or is it least surprising to let the 100 line window
slide to the end, and only deal with the last hundred?

--Kyle

Reading the example you gave, and even some on the net, it looks like
I'd need the Array class to have a coerce method that can change an
Array into a FixedSizeArray?

No. Your coerce method needs to take care. Look at my example. I
did not change Fixnum or Float.

Right, you didn't change it, but in the example you're calling coerce
on whatever it is that's passed in (perhaps this is the source of my
confusion):

Class X
... most everything removed ...
def + other
   if self.class === other
     # fake only
     self.class.new
   else
     #only going to get here if other isn't of class X
     a, b = other.coerce(self)
     #doesn't whatever class other is have to have a coerce method?
     a + b
   end
end
end

Now if we pull up irb.
fixnum=10
float=11.0
hash={}
array=
string="Wax Rabbits"

fixnum.respond_to?"coerce"
=> true
float.respond_to?"coerce"
=> true
hash.respond_to?"coerce"
=> false
array.respond_to?"coerce"
=> false
string.respond_to?"coerce"
=> false

So since Arrays Hashes and Strings don't have a coerce, does that mean
they can't be used in this fashion?

That's ok but that does not mean that a FixedSizedArray *is an* Array.
I was talking about semantics of inheritance which is usually denoted
*is a* relationship.

Cheers

robert

Thanks
--Kyle

···

On Thu, Sep 25, 2008 at 1:31 PM, Robert Klemme <shortcutter@googlemail.com> wrote:

2008/9/25 Kyle Schmitt <kyleaschmitt@gmail.com>:

Reading the example you gave, and even some on the net, it looks like
I'd need the Array class to have a coerce method that can change an
Array into a FixedSizeArray?

No. Your coerce method needs to take care. Look at my example. I
did not change Fixnum or Float.

Right, you didn't change it, but in the example you're calling coerce
on whatever it is that's passed in (perhaps this is the source of my
confusion):

The critical thing to understand is that the _other_ instance's coerce
method is invoked!

13:38:43 ~$ ruby -r bigdecimal -e
'bd=BigDecimal.new("2");set_trace_func lambda {|*a|p a};1+bd'
["line", "-e", 1, nil, #<Binding:0x1002e3c8>, false]
["c-call", "-e", 1, :+, #<Binding:0x1002e38c>, Fixnum]
["c-call", "-e", 1, :coerce, #<Binding:0x1002e274>, BigDecimal]
["c-return", "-e", 1, :coerce, #<Binding:0x1002e080>, BigDecimal]
["c-call", "-e", 1, :+, #<Binding:0x1002e044>, BigDecimal]
["c-return", "-e", 1, :+, #<Binding:0x1002de8c>, BigDecimal]
["c-return", "-e", 1, :+, #<Binding:0x1002de50>, Fixnum]

Look at trace lines 2 and 3: Fixnum#+ is invoked and then BigDecimal#coerce.

Class X
... most everything removed ...
def + other
  if self.class === other
    # fake only
    self.class.new
  else
    #only going to get here if other isn't of class X
    a, b = other.coerce(self)
    #doesn't whatever class other is have to have a coerce method?
    a + b
  end
end
end

Now if we pull up irb.
fixnum=10
float=11.0
hash={}
array=
string="Wax Rabbits"

fixnum.respond_to?"coerce"
=> true
float.respond_to?"coerce"
=> true
hash.respond_to?"coerce"
=> false
array.respond_to?"coerce"
=> false
string.respond_to?"coerce"
=> false

So since Arrays Hashes and Strings don't have a coerce, does that mean
they can't be used in this fashion?

13:39:04 ~$ ruby -e '1+'
-e:1:in `+': Array can't be coerced into Fixnum (TypeError)
        from -e:1
13:41:04 ~$ ruby -e '+1'
-e:1:in `+': can't convert Fixnum into Array (TypeError)
        from -e:1
13:41:12 ~$ ruby -e '1+{}'
-e:1:in `+': Hash can't be coerced into Fixnum (TypeError)
        from -e:1
13:41:19 ~$ ruby -e '{}+1'
-e:1: undefined method `+' for {}:Hash (NoMethodError)

Cheers

robert

···

2008/9/25 Kyle Schmitt <kyleaschmitt@gmail.com>:

On Thu, Sep 25, 2008 at 1:31 PM, Robert Klemme > <shortcutter@googlemail.com> wrote:

2008/9/25 Kyle Schmitt <kyleaschmitt@gmail.com>:

--
use.inject do |as, often| as.you_can - without end