the dark side of inherited methods

T

timr

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
 
P

Phillip Gawlowski

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. ;)

--
Phillip Gawlowski

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

Rajinder Yadav

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. ;)

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 Yadav | 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
 
P

Phillip Gawlowski

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 Gawlowski

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

James Edward Gray II

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:
=20
class Vector < Array
def initialize(*arr)
super(arr.flatten)
end
end
=20
v =3D Vector.new(1,2,3) # =3D> [1, 2, 3]
v.class # =3D> Vector
v.collect{|item| item * 3}.class # =3D> Array
v.collect!{|item| item * 3}.class # =3D> Vector
=20
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 =3D array.flatten
end
=20
def class
::Vector
end
=20
def method_missing(meth, *args, &blk)
result =3D @array.send(meth, *args, &blk)
if result.object_id =3D=3D @array.object_id
self
elsif result.class =3D=3D ::Array
self.class.new(result)
else
result
end
end
end

v =3D Vector.new(1,2,3) # =3D> [1, 2, 3]
v.class # =3D> Vector
v.collect { |item| item * 3 }.class # =3D> Array
v.collect!{ |item| item * 3 }.class # =3D> 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 Gray II=
 
R

Rajinder Yadav

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.

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 Yadav | 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
 
T

timr

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

Robert Dober

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

HTH
R.
 
D

Daniel Berger

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
=A0def initialize(*arr)
=A0 =A0super(arr.flatten)
=A0end
end
v =3D Vector.new(1,2,3) # =3D> [1, 2, 3]
v.class # =3D> Vector
v.collect{|item| item * 3}.class # =3D> Array
v.collect!{|item| item * 3}.class # =3D> 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 al=
most never what we want. =A0Luckily, this is Ruby where anything is possibl=
e:
class Vector < BasicObject
=A0 def initialize(*array)
=A0 =A0 @array =3D array.flatten
=A0 end

=A0 def class
=A0 =A0 ::Vector
=A0 end

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

That's a solution for Ruby 1.9, but a similar trick is possible with 1.8 =
using a touch more code. =A0It'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
 
J

James Edward Gray II

On Oct 31, 2010, at 5:30 PM, timr wrote:
=20
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: =20
class Vector < Array
def initialize(*arr)
super(arr.flatten)
end
end =20
v =3D Vector.new(1,2,3) # =3D> [1, 2, 3]
v.class # =3D> Vector
v.collect{|item| item * 3}.class # =3D> Array
v.collect!{|item| item * 3}.class # =3D> Vector =20
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?
=20
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:
=20
class Vector < BasicObject
def initialize(*array)
@array =3D array.flatten
end
=20
def class
::Vector
end
=20
def method_missing(meth, *args, &blk)
result =3D @array.send(meth, *args, &blk)
if result.object_id =3D=3D @array.object_id
self
elsif result.class =3D=3D ::Array
self.class.new(result)
else
result
end
end
end
v =3D Vector.new(1,2,3) # =3D> [1, 2, 3]
v.class # =3D> Vector
v.collect { |item| item * 3 }.class # =3D> Array
v.collect!{ |item| item * 3 }.class # =3D> Vector
=20
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.
=20
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. :)

James Edward Gray II
 
T

Tony Arcieri

[Note: parts of this message were removed to make it a legal post.]

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'"
 
R

Robert Dober

It is a problem with inheritance and another reason why inheritance isI 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
 
R

Robert Klemme

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
 
J

James Edward Gray II

=20
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. =20

Of course, there are various forms of inheritance. It's "implementation =
inheritance" that's the main problem.=20

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 Gray II=
 
I

Intransition

I believe this is what James wanted to stress: inheritance is used far
too often for purposes where it's not appropriate. =A0A 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).

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 =3D Foo2

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

Josh Cheek

[Note: parts of this message were removed to make it a legal post.]

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

Robert Klemme

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.

I would not subscribe to that. Although Bertrand Meyer is a big fan of
implementation inheritance I am not yet convinced that it is such a good
idea so often. Of course, there are always uses for any technique
present, but I find the "is a" relationship interpretation of
inheritance so clear and obvious that I somehow feel bad about polluting
inheritance with other uses. I cannot really put forward a more
concrete argument or even back this up by some hard (business) numbers,
but a world in which "A inherits B" <=> "A is a B" is so much simpler.
And in Ruby, whenever you need implementation inheritance you can use a
mixin module. Enumerable is a very good example for that.
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

Well, there are other forms of code reuse - even in procedural
languages. It's not that you _need_ inheritance to have code reuse. I
would even go as far as to claim that it is more difficult to implement
classes intended solely for implementation inheritance than classes
which implement a particular real world concept and which are only
inherited if "is a" is needed. I say this because a class intended for
implementation inheritance needs other criteria for including
functionality than a class which implements a particular concept.
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.

That does not sound like a, um, proper application of inheritance to me.
It might be useful to do it that way in some rare conditions but I
would not promote this as a big feature of inheritance.
Sadly, I had to inform him that OOP did not quite prove to be the
godsend everyone originally thought it might be.

Well, silver bullets - if you know one, you know them all. :)

Btw, I find that we have this type of discussion far too seldom here
nowadays and I imagine that we did that more often earlier on. But that
is totally subjective.

Cheers

robert
 
J

Joel VanderWerf

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

The shortest example that occurs to me:

class A < Struct.new :x, :y, :z
def length
Math.sqrt(x**2 + y**2 + 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.
 
J

James Edward Gray II

=20
The shortest example that occurs to me:
=20
class A < Struct.new :x, :y, :z
def length
Math.sqrt(x**2 + y**2 + z**2)
end
end
=20
That only saves a couple of keystrokes compared to:
=20
A =3D Struct.new :x, :y, :z
class A
...
=20
but it's cleaner IMO. Also, it allows you to do this:
=20
def x=3D(new_x)
raise ArgumentError if new_x > 100
super
end
=20
In the "A=3DStruct.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 =3D Struct.new:)x, :y, :z) do
def length
Math.sqrt(x**2 + y**2 + z**2)
end
end

James Edward Gray II=
 

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,770
Messages
2,569,583
Members
45,074
Latest member
StanleyFra

Latest Threads

Top