Inheriting Array and slice() behaviour

S

Sylvain Joyeux

I have a class inheriting Array, and I expected slice() and []
to return Array objects. Unfortunately

irb(main):001:0> class Expression < Array
irb(main):002:1> end
=> nil
irb(main):003:0> test = Expression.new
=> []
irb(main):004:0> test.concat( (1..10).to_a )
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
irb(main):005:0> test.slice(1..5)
=> [2, 3, 4, 5, 6]
irb(main):006:0> test.slice(1..5).class
=> Expression

And I don't understand why ...
Moreover, since my Expression class has instance variables, the instance
produced by slice() does not have it anymore (which is bad)

I wokarounded this by using Delegator
Any idea on why this behaviour ?

Sylvain
 
W

William Morgan

Excerpts from Sylvain Joyeux's mail of 6 Jul 2005 (EDT):
I have a class inheriting Array, and I expected slice() and []
to return Array objects. Unfortunately [...]
Moreover, since my Expression class has instance variables, the instance
produced by slice() does not have it anymore (which is bad)

It looks like [], slice and slice! methods create a new instance of the
objects class as the return value, but don't initialize it---so any
instance variables will be nil (rb_ary_subseq). I agree that this is
weird.
I wokarounded this by using Delegator

What return type do you want? Arrays or Expressions? You can force
return values back to Arrays with #to_a, or manually make Expressions
with something like

y = Expression.new(x.slice(1..5).to_a)
 
S

Sylvain Joyeux

What return type do you want? Arrays or Expressions? You can force
return values back to Arrays with #to_a, or manually make Expressions
with something like

y = Expression.new(x.slice(1..5).to_a)

I find it VERY weird to build anything else than an Array here. Array now
nothing about Expression, so I don't see how - in general - it could build
a well-formed Expression object

Sylvain
 
D

David A. Black

Hi --

I find it VERY weird to build anything else than an Array here. Array now
nothing about Expression, so I don't see how - in general - it could build
a well-formed Expression object

But remember... you're not calling Array#slice; you're calling
Expression#slice. It may reside in Array, but that doesn't mean it
has to return an array. It might assume that anything derived from
Array is "sliceable", so to speak.

By way of contrast:

class A < Array; end
a = A.new.concat([1,2,3,4])
a.select {|x| x < 3 } .class # Array (not A)

In that case, Array is used as the "normalized" form for returning
results, because Enumerable#select cannot know whether A objects are
suitable as containers. (At least, that's how I've understood it.)


David
 
D

Daniel Brockman

Sylvain Joyeux said:
I have a class inheriting Array,

Hi Sylvain,

I'm sorry, but this is asking for trouble.
and I expected slice() and [] to return Array objects.

I think I'd expect them to return instances of the derived class.
Surely a subrange of a Foo is also a Foo?
Unfortunately

irb(main):001:0> class Expression < Array
irb(main):002:1> end
=> nil
irb(main):003:0> test = Expression.new
=> []
irb(main):004:0> test.concat( (1..10).to_a )
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
irb(main):005:0> test.slice(1..5)
=> [2, 3, 4, 5, 6]
irb(main):006:0> test.slice(1..5).class
=> Expression

And I don't understand why ...

Looking through array.c, it's pretty easy to see why this happens on a
purely mechanical level --- I'll walk you through it.

We'll start in `Init_Array':

rb_define_method(rb_cArray, "[]", rb_ary_aref, -1);
rb_define_method(rb_cArray, "slice", rb_ary_aref, -1);

If we call this method with two arguments, the following will happen:

VALUE
rb_ary_aref(argc, argv, ary)
int argc;
VALUE *argv;
VALUE ary;
{
VALUE arg;
long beg, len;

if (argc == 2) {

[...]

beg = NUM2LONG(argv[0]);
len = NUM2LONG(argv[1]);

[...]

return rb_ary_subseq(ary, beg, len);
}

[...]
}

static VALUE
rb_ary_subseq(ary, beg, len)
VALUE ary;
long beg, len;
{
VALUE klass, ary2, shared;

[...]

klass = rb_obj_class(ary);

[...]

ary2 = ary_alloc(klass);

[...]

return ary2;
}

static VALUE
ary_alloc(klass)
VALUE klass;
{
NEWOBJ(ary, struct RArray);
OBJSETUP(ary, klass, T_ARRAY);

[...]

return (VALUE)ary;
}

#define OBJSETUP(obj,c,t) do {\
RBASIC(obj)->flags = (t);\
RBASIC(obj)->klass = (c);\
if (rb_safe_level() >= 3) FL_SET(obj, FL_TAINT);\
} while (0)

However, I'm ignorant about the rationale of this behavior.
Moreover, since my Expression class has instance variables, the
instance produced by slice() does not have it anymore (which is bad)

That is bad. But I'm not sure whether it could be easily fixed;
arrays are pretty special in Ruby. For example, when you say
foo[1..5], a new array is returned, but no elements are copied.
I wokarounded this by using Delegator
Any idea on why this behaviour ?

Although I agree that it would be nice if inheriting from Array worked
painlessly, that is unfortunately not (currently) the case.

If you are looking for practical advice, I say avoid inheriting from
core classes like Array, and stick with Delegator.

On the other hand, if you are seeking to discuss the possibilities of
changing how Array is implemented, don't let me stand in your way.
 
W

William Morgan

Excerpts from Sylvain Joyeux's mail of 6 Jul 2005 (EDT):
I find it VERY weird to build anything else than an Array here. Array
now nothing about Expression, so I don't see how - in general - it
could build a well-formed Expression object

I agree with you. I think this is bad behavior.
 
Y

Yukihiro Matsumoto

Hi,

In message "Re: Inheriting Array and slice() behaviour"

|I have a class inheriting Array, and I expected slice() and []
|to return Array objects.

Why? I'd expect them to return instances of the class of the
receiver, as Daniel does in [ruby-talk:147323], since they are ripped
part of the receiver.

|Moreover, since my Expression class has instance variables, the instance
|produced by slice() does not have it anymore (which is bad)

I agree that is bad. It might be possible to call initialize() at the
time of sub-array allocation, at the cost of performance. Besides
that, we have to make consensus for how initialize() will be called
for those sub-array.

Anyway, I'd suggest you not to subclass basic classes, unless you are
sure what you're doing. I don't think Expressions by no means have
is-a relationship to Arrays. Inheritance is not a panacea.

matz.
 
A

Ara.T.Howard

Hi,

In message "Re: Inheriting Array and slice() behaviour"

|I have a class inheriting Array, and I expected slice() and []
|to return Array objects.

Why? I'd expect them to return instances of the class of the
receiver, as Daniel does in [ruby-talk:147323], since they are ripped
part of the receiver.

|Moreover, since my Expression class has instance variables, the instance
|produced by slice() does not have it anymore (which is bad)

I agree that is bad. It might be possible to call initialize() at the
time of sub-array allocation, at the cost of performance. Besides
that, we have to make consensus for how initialize() will be called
for those sub-array.

Anyway, I'd suggest you not to subclass basic classes, unless you are
sure what you're doing. I don't think Expressions by no means have
is-a relationship to Arrays. Inheritance is not a panacea.

you suggest a has-a or aggregation approach then?

-a
--
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| My religion is very simple. My religion is kindness.
| --Tenzin Gyatso
===============================================================================
 
S

Sylvain Joyeux

This is a lisp-like language, so expressions are basically array of atoms
and expressions. When I do a slice() this is to get a subset of the list,
but a subset is not an expression itself.

Expression is-a Array, but Expression.slice is just an Array

Sylvain
 
A

Ara.T.Howard

This is a lisp-like language, so expressions are basically array of atoms
and expressions. When I do a slice() this is to get a subset of the list,
but a subset is not an expression itself.

Expression is-a Array, but Expression.slice is just an Array

Sylvain

harp:~ > cat a.rb
class Expression < ::Array
munged_return_methods = %w(
slice
)
munged_return_methods.each{|m| eval "def #{ m }(*a, &b); Array[*super] ;end"}
end

exp = Expression['a', 'b', 'c']

p(exp.slice(0,2).class)

harp:~ > ruby a.rb
Array

-a
--
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| My religion is very simple. My religion is kindness.
| --Tenzin Gyatso
===============================================================================
 
R

Robert Klemme

William Morgan said:
Excerpts from Sylvain Joyeux's mail of 6 Jul 2005 (EDT):

I agree with you. I think this is bad behavior.

I wouldn't be so certain of this. It's normal for operations like #dup and
#clone to behave that way. Also, part of the smartness of ruby is that you
can simply do self.class.new to get a new instance of the same class. And
often you need to create an instance of the same class. That's not too
abnormal.

IMHO if there is a problem then with proper initialization of the new
instance. OTOH, inheriting classes like Array is seldom really good. There
are too many methods that return plain arrays and you end up with Arrays
anyway. Delegation, ad hoc modification or extension with a Module is often
better.

Kind regards

robert
 
P

Patrick Gundlach

[...]
OTOH, inheriting classes like Array is seldom really good.
There are too many methods that return plain arrays and you end up
with Arrays anyway. Delegation, ad hoc modification or extension with
a Module is often better.

Now it is the second time I've read this. Currently I have a class
that stores an 8 bit encoding. The elements of this class are accessed
directly just like an array. So now I have

class ENC < Array

attr_accessor :foo

def initialize(fileobject=nil)
# if fileobject then initialize 256 elements with the contents
# of the file
# else
# super(256,"undefined slot")
end
def somefunction
# read the contents of the 256 and store the result of
# some operation in @foo
end
end

So I get [], []= and a lot of things for free. My subclass is basicly
an array with some additional methods attached. What do you suggest
instead? Something like

class ENC < DelegateClass(Array)
...
end

?


Patrick
 
W

William Morgan

Excerpts from Yukihiro Matsumoto's mail of 6 Jul 2005 (EDT):
on Wed, 6 Jul 2005 18:17:21 +0900, Sylvain Joyeux

|I have a class inheriting Array, and I expected slice() and [] |to
return Array objects.

Why? I'd expect them to return instances of the class of the
receiver, as Daniel does in [ruby-talk:147323], since they are ripped
part of the receiver.

But #+ returns an Array. Is that consistent?

Like the OP, I think it's weird and special-cased to have #slice
magically return something with the receiver's class. Especially since
the return value is going to be broken (not initialized) except in
trivial cases.
Anyway, I'd suggest you not to subclass basic classes, unless you are
sure what you're doing. I don't think Expressions by no means have
is-a relationship to Arrays. Inheritance is not a panacea.

Absolutely.
 
Y

Yukihiro Matsumoto

Hi,

In message "Re: Inheriting Array and slice() behaviour"

|> Why? I'd expect them to return instances of the class of the
|> receiver, as Daniel does in [ruby-talk:147323], since they are ripped
|> part of the receiver.
|
|But #+ returns an Array. Is that consistent?

The result from + method is not a sub-array. Where X = A + B,
the possible choices of the class of X are:

(a) assured common ancestor, i.e. Array
(b) lowest common ancestor of A and B
(c) class of A if A and B are instances of same class, Array otherwise

Currently (a) is my decision. (b) is too expensive, I guess. (c)
might be a good choice, but I'm not sure whether it's more useful than
the current one.

|Like the OP, I think it's weird and special-cased to have #slice
|magically return something with the receiver's class. Especially since
|the return value is going to be broken (not initialized) except in
|trivial cases.

What if it can be initialized properly?

matz.
 
Y

Yukihiro Matsumoto

Hi,

In message "Re: Inheriting Array and slice() behaviour"

|> Anyway, I'd suggest you not to subclass basic classes, unless you are
|> sure what you're doing. I don't think Expressions by no means have
|> is-a relationship to Arrays. Inheritance is not a panacea.
|
|you suggest a has-a or aggregation approach then?

It depends on what the original poster want to accomplish.

matz.
 
Y

Yukihiro Matsumoto

Hi,

In message "Re: Inheriting Array and slice() behaviour"

|This is a lisp-like language, so expressions are basically array of atoms
|and expressions. When I do a slice() this is to get a subset of the list,
|but a subset is not an expression itself.
|
|Expression is-a Array, but Expression.slice is just an Array

In Lisp, a sub-list from a list is a list (or atom), not an array.
But anyway, I understand what you want.

matz.
 
D

David A. Black

Hi --

This is a lisp-like language, so expressions are basically array of atoms
and expressions. When I do a slice() this is to get a subset of the list,
but a subset is not an expression itself.

Expressions may be arrays or lists of atoms and expressions in a
sense, but they're not actually instances of the Ruby class Array.
Expression is-a Array, but Expression.slice is just an Array

That's true, by definition, if you decide up front that a slice operation
has to return an Array object. I don't think I would, though. I'd
actually tend to think of a "slice" of something as being "cut from
the same cloth" (i.e., of the same class) as the original -- as
opposed to, say, the results of a map operation, where you're
transforming elements and then need a general-purpose container (an
array) to store them in.


David
 
R

Robert Klemme

Patrick said:
[...]
OTOH, inheriting classes like Array is seldom really good.
There are too many methods that return plain arrays and you end up
with Arrays anyway. Delegation, ad hoc modification or extension
with a Module is often better.

Now it is the second time I've read this. Currently I have a class
that stores an 8 bit encoding. The elements of this class are accessed
directly just like an array. So now I have

class ENC < Array

attr_accessor :foo

def initialize(fileobject=nil)
# if fileobject then initialize 256 elements with the contents
# of the file
# else
# super(256,"undefined slot")
end
def somefunction
# read the contents of the 256 and store the result of
# some operation in @foo
end
end

So I get [], []= and a lot of things for free. My subclass is basicly
an array with some additional methods attached. What do you suggest
instead? Something like

class ENC < DelegateClass(Array)
...
end

Yep. Or do it selectively (what I'd prefer). This can help:

class Module
def delegate(member, method)
class_eval "def #{method}(*a,&b) @#{member}.#{method}(*a,&b) end"
end
end
"def size(*a,&b) @s.size(*a,&b) end"
=> nil=> 3

Btw, IMHO this would be a good addition to delegate.rb. What do others
think?

Kind regards

robert
 
P

Patrick Gundlach

[...]

Great, that works fine with all my tests :) Somehow I was always
afraid of delegate.rb. Don't ask me why, perhaps mostly because it is
not shipped with lots of examples where it shows its advantage over
other approaches (in my case: subclassing Array directly).
Or do it selectively (what I'd prefer).

I use my class to build a library. So I don't know in advance what
methods the users would like use. So is there a warning attached to
the above?


Thanks for your advice.

Patrick
 

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

Similar Threads


Members online

No members online now.

Forum statistics

Threads
473,754
Messages
2,569,528
Members
45,000
Latest member
MurrayKeync

Latest Threads

Top