The dark side of inherited methods

Let’s say I want to make a new class, Vector (that will function,
eventually, like R vectors for mathematical operations), and I write
this:

class Vector < Array
def initialize(*arr)
super(arr.flatten)
end
end

v = Vector.new(1,2,3) # => [1, 2, 3]
v.class # => Vector
v.collect{|item| item * 3}.class # => Array
v.collect!{|item| item * 3}.class # => Vector

I noticed that I inherited the Array#collect and Array#collect!
methods. Yeah for inheritance! But then look at the returned classes.
Array#collect returns and array. Array.collect! returns a Vector. If
you look at the underlying C code that defines these methods the
response makes sense. Collect! alters the object itself using the
results from the block, while collect builds a new Array from the
results of the block. But geesh, that means to have consistent
behavior, I have to add the following (and worse, look through every
other Array method to determine if the return value should be a vector
rather than an array):

class Vector < Array
def collect(&blk)
Vector.new(super) #gets the return from super and puts that
back into a vector object
end
end

v = Vector.new(1,2,3) # => [1, 2, 3]
v.class # => Vector
v.collect{|item| item * 3}.class # => Vector
v.collect!{|item| item * 3}.class # => Vector

When a method has the form of array -> an_array I want it to take a
vector -> a_vector, but I get vector -> array. Is there a simple way
to fix this for all similar methods? Or do I just have to live with
the dark side of inherited methods?
Thanks,
Tim

On Sun, Oct 31, 2010 at 11:30 PM, timr [email protected] wrote:

I noticed that I inherited the Array#collect and Array#collect!
methods. Yeah for inheritance! But then look at the returned classes.
Array#collect returns and array. Array.collect! returns a Vector. If
you look at the underlying C code that defines these methods the
response makes sense. Collect! alters the object itself using the
results from the block, while collect builds a new Array from the
results of the block. But geesh, that means to have consistent
behavior, I have to add the following (and worse, look through every
other Array method to determine if the return value should be a vector
rather than an array):

Define “consistent behavior”. That #collect returns an Array is
consistent with other, similar language constructs. The question is if
you want an Array back, or want a Vector back from the operation you
perform. Similarly, since you inherited the Array methods, Vector#join
will behave exactly like Array#join.

That’s pretty much the point of inheritance: Re-using a class’s
methods to make your own life easier by reducing the work you need to
do, since most methods you expect to use are identical to what you
need, and only need adaptation to your own needs (via encapsulation,
or overriding, for example).

Maybe you have better success (i.e. less work), if you a) encapsulate
similar calls into calls of your own and massage the operation that
way, or b) you move one up in the inheritance change, and use
Enumerable to inherit from. Most work will be c) create your class
from scratch, and mix in Array or Enumerable.

TL;DR: Any class derived from another class will inherit its method.
That’s true for Ruby, as it is for Java or C#. It’s pretty much the
point of inheritance. :wink:


Phillip G.

Though the folk I have met,
(Ah, how soon!) they forget
When I’ve moved on to some other place,
There may be one or two,
When I’ve played and passed through,
Who’ll remember my song or my face.

On 10-10-31 06:42 PM, Phillip G. wrote:

rather than an array):
need, and only need adaptation to your own needs (via encapsulation,
point of inheritance. :wink:

I see how Vector is-a Array, I don’t see how Array is-a Vector? I would
consider collect and collect! to be inconsistent under this definition.


Kind Regards,
Rajinder Y. | DevMentor.org | Do Good! ~ Share Freely

GNU/Linux: 2.6.35-22-generic
Kubuntu x86_64 10.10 | KDE 4.5.1
Ruby 1.9.2p0 | Rails 3.0.1

On Sun, Oct 31, 2010 at 11:54 PM, Rajinder Y. [email protected]
wrote:

I see how Vector is-a Array, I don’t see how Array is-a Vector? I would
consider collect and collect! to be inconsistent under this definition.

Array#collect returns a new Array. Array#collect! returns the existing
Array. Thus, Vector#collect returns a new Array, and Vector#collect!
returns the existing Vector. Bang-methods operate on the object
itself, and return the modified object, instead of creating a new
object containing the result of the operation the non-Bang-method
performed.

The behavior is consistent with how methods with and without Bang
operate.


Phillip G.

Though the folk I have met,
(Ah, how soon!) they forget
When I’ve moved on to some other place,
There may be one or two,
When I’ve played and passed through,
Who’ll remember my song or my face.

On Oct 31, 2010, at 5:30 PM, timr wrote:

v = Vector.new(1,2,3) # => [1, 2, 3]
v.class # => Vector
v.collect{|item| item * 3}.class # => Array
v.collect!{|item| item * 3}.class # => Vector

I noticed that I inherited the Array#collect and Array#collect!
methods. Yeah for inheritance! But then look at the returned classes.
Array#collect returns and array. Array.collect! returns a Vector.

Is there a simple way to fix this for all similar methods? Or do I just have to
live with the dark side of inherited methods?

It is a problem with inheritance and another reason why inheritance is
almost never what we want. Luckily, this is Ruby where anything is
possible:

class Vector < BasicObject
def initialize(*array)
@array = array.flatten
end

def class
::Vector
end

def method_missing(meth, *args, &blk)
result = @array.send(meth, *args, &blk)
if result.object_id == @array.object_id
self
elsif result.class == ::Array
self.class.new(result)
else
result
end
end
end

v = Vector.new(1,2,3) # => [1, 2, 3]
v.class # => Vector
v.collect { |item| item * 3 }.class # => Array
v.collect!{ |item| item * 3 }.class # => Vector

That’s a solution for Ruby 1.9, but a similar trick is possible with 1.8
using a touch more code. It’s probably not perfect yet, but you get the
idea.

James Edward G. II

On 10-10-31 07:00 PM, Phillip G. wrote:

The behavior is consistent with how methods with and without Bang operate.

Thanks Phillip, yes I agree with respect to the implementation, i get
it. was mulling that over after i hit send =P


Kind Regards,
Rajinder Y. | DevMentor.org | Do Good! ~ Share Freely

GNU/Linux: 2.6.35-22-generic
Kubuntu x86_64 10.10 | KDE 4.5.1
Ruby 1.9.2p0 | Rails 3.0.1

It is a problem with inheritance and another reason why inheritance is
almost never what we want.

Tim: Glad that I am not the only one who thinks inheritance has some
holes.
Tim: Note the corrected output from James Edward G. II’s response:

class Vector < Object
def initialize(*array)
@array = array.flatten
end
def class
::Vector
end
def method_missing(meth, *args, &blk)
result = @array.send(meth, *args, &blk)
if result.object_id == @array.object_id
self
elsif result.class == ::Array
self.class.new(result)
else
result
end
end
end
v = Vector.new(1,2,3) # => #<Vector:0x10183c758
@array=[1, 2, 3]>
v.class # => Vector
v.collect { |item| item * 3 }.class # => Vector
v.collect!{ |item| item * 3 }.class # => Vector

That’s a solution for Ruby 1.9, but a similar trick is possible with
1.8 using a touch more code. It’s probably not perfect yet, but you
get the idea.

Tim: It actually runs fine in 1.8.7 just as you have it coded.
Tim: However, I already blew my method_missing to over-ride the
mathematical operators (see below):
Tim: But I get your approach and I will think about something along
those lines. It could also work to set up a proxy class and use its
method_missing (since I already used Vector’s method_missing) to fix
the type returned the way you did. But that means v -
Vector.new(1,1,1); p_of_v = Proxy.new(v) every time I want to use a
vector. Still not ideal.

class Vector < Array
undef :-, :+, :*, :==

def initialize(*arr)
v = super(arr.flatten)
end

def to_a
Array.new(self)
end

def &(arg)
Vector.new(super)
end

def |(arg)
Vector.new(super)
end

def collect(&blk)
Vector.new(super)
end

def select(&blk)
Vector.new(super)
end

def method_missing(meth, arg)
arg.class == Vector ? a2 = arg : a2 = Vector.new(arg)
long, short = [self, a2].sort{|s,b| b.length <=> s.length}
empty_collection = Array.new(long.length)

arr_result = empty_collection.each_with_index.map do |item, ind|
  if self[ind%self.length] == nil || a2[ind%a2.length] == nil

#allow nils to be mathematically processed
nil
else
self[ind%self.length].send(meth, a2[ind%a2.length])
end
end
Vector.new(arr_result)
end
end

Example Usage:
a = Vector.new([1,2,1,10])
b = Vector.new([2,5,1,7])
a - b # => [-1, -3, 0, 3]
a.uniq.class # => Vector
(a | b) # => [1, 2, 10, 5, 7]
(a | b).class # => Vector

On Mon, Nov 1, 2010 at 12:49 AM, James Edward G. II
[email protected] wrote:

On Oct 31, 2010, at 5:30 PM, timr wrote:

It is a problem with inheritance and another reason why inheritance is almost
never what we want.

Completely agree with this. Apart of the code you suggested I would
like to tackle the problem on a conceptional level. Enumerable#map
returns arrays Period. What OP might want to do is something like this

module Sequence
include Enumerable

A sequence is defined by

(i) How to traverse it

def each &blk

making sure that blk is called with exactly all elements of the

sequence in the

predefined order

(ii) How to append elements to it

def add new_element

creating a new sequence (or modifying in place)

def map &blk
inject(self.class.new) do | result, ele |
result.add blk.( ele )
end
end
end # module Sequence

If OP had had this available the delegation would have worked quite
well.

class Vector
include Sequence
def each; # as he needs (must yield all elements)
def add; # as he needs (must return a Sequence)

No inheritance and yet quite some effective decomposition :stuck_out_tongue:

HTH
R.

On Nov 1, 2010, at 4:32 PM, Daniel B. wrote:

super(arr.flatten)
Array#collect returns and array. Array.collect! returns a Vector.
::Vector
end
end
end
v = Vector.new(1,2,3) # => [1, 2, 3]
v.class # => Vector
v.collect { |item| item * 3 }.class # => Array
v.collect!{ |item| item * 3 }.class # => Vector

That’s a solution for Ruby 1.9, but a similar trick is possible with 1.8 using
a touch more code. It’s probably not perfect yet, but you get the idea.

Eeep, method_missing.

My first reaction when I saw the original code was, “Eeep, inheriting
from a core class.”

I guess we both have some fear to get past. :slight_smile:

James Edward G. II

On 01.11.2010 22:55, James Edward G. II wrote:

My first reaction when I saw the original code was, “Eeep, inheriting
from a core class.”

+1 (me too)

I guess we both have some fear to get past. :slight_smile:

Well, I don’t think it is a question of fear. :slight_smile:

Cheers

robert

On Oct 31, 5:49pm, James Edward G. II [email protected]
wrote:

Let’s say I want to make a new class, Vector (that will function,
v.class # => Vector
class Vector < BasicObject
if result.object_id == @array.object_id
v.collect { |item| item * 3 }.class # => Array
v.collect!{ |item| item * 3 }.class # => Vector

That’s a solution for Ruby 1.9, but a similar trick is possible with 1.8 using a
touch more code. It’s probably not perfect yet, but you get the idea.

Eeep, method_missing. I was thinking this might be accomplished via
some sort of AOP. I looked at AspectR and Aquarium but couldn’t make
it work. Maybe that’s the wrong approach, though.

Regards,

Dan

It is a problem with inheritance and another reason why inheritance is
almost never what we want.

I cannot speak for Edward but would like to defend my (+1) on this.
Actually there is nothing to defend because I did not read correctly
(good :(). I read inheritance from a “core class”, which was not
written. And then I continued to read text that was not there:
“inheritance from a core class using delegation to #each”.
That said I should probably apologize :P, so I do. But I guess that is
where the “problem” comes from.
Cheers
Robert

On Sun, Oct 31, 2010 at 5:49 PM, James Edward G. II
<[email protected]

wrote:

It is a problem with inheritance and another reason why inheritance is
almost never what we want.

That’s a pretty extreme position to take. Inheritance is an
indispensable
tool. That said, I will agree that “is a” relationships come up far less
frequently than 'has a" relationships.

I would say that it would make far more sense to create a Vector class
which
“has a”(n) array, and make the Vector an Enumerable. Of course, what
makes
even more sense is to use the Vector class provided by “require
‘matrix’”

On Tue, Nov 2, 2010 at 4:29 AM, Tony A. [email protected]
wrote:

On Sun, Oct 31, 2010 at 5:49 PM, James Edward G. II <[email protected]

wrote:

It is a problem with inheritance and another reason why inheritance is
almost never what we want.

That’s a pretty extreme position to take. Inheritance is an indispensable
tool. That said, I will agree that “is a” relationships come up far less
frequently than 'has a" relationships.

I believe this is what James wanted to stress: inheritance is used far
too often for purposes where it’s not appropriate. A real is_a
relationship is pretty rare yet many people use inheritance,
especially with core / std lib classes as parents which almost always
is a code smell.

Side note: there are languages such as Eiffel and C++ which have
support for implementation inheritance (i.e. the interface changes
incompatibly from super class to sub class) and in those languages
inheritance can be used more often than in language which lack these
features (e.g. Java).

I would say that it would make far more sense to create a Vector class which
“has a”(n) array, and make the Vector an Enumerable. Of course, what makes
even more sense is to use the Vector class provided by “require ‘matrix’”

Absolutely.

Kind regards

robert

On Nov 1, 2010, at 10:29 PM, Tony A. wrote:

On Sun, Oct 31, 2010 at 5:49 PM, James Edward G. II <[email protected]

wrote:

It is a problem with inheritance and another reason why inheritance is
almost never what we want.

That’s a pretty extreme position to take. Inheritance is an indispensable tool.

There have been a lot of books written about the evils of inheritance.
It’s mentioned on the Wikipedia page for inheritance. I thought it was
pretty much a given at this point.

Of course, there are various forms of inheritance. It’s “implementation
inheritance” that’s the main problem.

Design Patterns could also be called Ways to Avoid (Implementation)
Inheritance, I think. I find that it’s even easier and more desirable
to avoid inheritance with a language as dynamic as Ruby.

Of course, inheritance has it’s place and uses but I feel it’s
definitely overused.

James Edward G. II

On Nov 2, 5:31am, Robert K. [email protected] wrote:

features (e.g. Java).
Indeed, I think inheritance gets a really bad rap in Ruby b/c Ruby’s
base classes and inheritance system are so poorly designed to handle
it.

The other day I was talking to my Father, a Cobol programmer from back
in the day, and he was telling me that when he retired, OOP was just
starting to get hyped. He was quite interested in it at the time and
then rattled off some of the advantages he remembered it was to bring
to the field. Inheritance for code reuse was high on the list and
related to that, one point eally struck me – instead of versioning
code as we have become accustomed, the idea was to subclass the
original class and implement changes in the subclass. Using some sort
of class name referencing scheme you could use any version. It sort of
reminded me of database migrations.

I imagined is something like:

class Foo1

end

class Foo2 < Foo1

end

#pick a version
Foo = Foo2

Sadly, I had to inform him that OOP did not quite prove to be the
godsend everyone originally thought it might be.

On Tue, Nov 2, 2010 at 11:19 AM, James Edward G. II
<[email protected]

wrote:

Of course, inheritance has it’s place and uses but I feel it’s definitely
overused.

Can you give some examples of where it has its place? I have been trying
to
think of one, and can’t. I realized the only time I ever subclass
anything
is ActiveRecord::Base (and I’m not really sure what that offers over
modules, DataMapper uses modules, for example).

On 11/02/2010 10:02 AM, Josh C. wrote:

modules, DataMapper uses modules, for example).
The shortest example that occurs to me:

class A < Struct.new :x, :y, :z
def length
Math.sqrt(x2 + y2 + z**2)
end
end

That only saves a couple of keystrokes compared to:

A = Struct.new :x, :y, :z
class A

but it’s cleaner IMO. Also, it allows you to do this:

def x=(new_x)
raise ArgumentError if new_x > 100
super
end

In the “A=Struct.new” approach, you have to muck around with alias to
get the same effect.

On Nov 2, 2010, at 1:04 PM, Joel VanderWerf wrote:

is ActiveRecord::Base (and I’m not really sure what that offers over
That only saves a couple of keystrokes compared to:
end

In the “A=Struct.new” approach, you have to muck around with alias to get the
same effect.

Neither are required though, since Struct.new() takes a block:

A = Struct.new(:x, :y, :z) do
def length
Math.sqrt(x2 + y2 + z**2)
end
end

James Edward G. II

On 11/02/2010 11:12 AM, James Edward G. II wrote:

A = Struct.new(:x, :y, :z) do
def length
Math.sqrt(x2 + y2 + z**2)
end
end

How do you fix this?

A = Struct.new :x, :y, :z do
def length
Math.sqrt(x2 + y2 + z**2)
end

def x=(new_x)
raise ArgumentError if new_x > 100
super
end
end

a = A.new
a.x = 1 # NoMethodError

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs