Getting the right class with inheritance and super()

K

Kyle Schmitt

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
 
D

David Masover

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

Robert Klemme

2008/9/25 David Masover said:
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.
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
 
K

Kyle Schmitt

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 :) that's
the only reason, but it's a very good point.
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
 
K

Kyle Schmitt

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 :)


Thanks!
--Kyle
 
R

Robert Klemme

2008/9/25 Kyle Schmitt said:
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.
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
 
K

Kyle Schmitt

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
 
K

Kyle Schmitt

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

Robert Klemme

2008/9/25 Kyle Schmitt said:
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.
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
 
M

Michael Fellinger

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


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
 
K

Kyle Schmitt

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
 
R

Robert Klemme

2008/9/25 Kyle Schmitt said:
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]
Fixnum] said:
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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,009
Latest member
GidgetGamb

Latest Threads

Top