Inheriting Array and slice() behaviour

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

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)

···

--
William <wmorgan-ruby-talk@masanjin.net>

Sylvain Joyeux <sylvain.joyeux@polytechnique.org> writes:

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.

···

--
Daniel Brockman <daniel@brockman.se>

    So really, we all have to ask ourselves:
    Am I waiting for RMS to do this? --TTN.

Hi,

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.

···

In message "Re: Inheriting Array and slice() behaviour" on Wed, 6 Jul 2005 18:17:21 +0900, Sylvain Joyeux <sylvain.joyeux@polytechnique.org> writes:

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

you suggest a has-a or aggregation approach then?

-a

···

On Thu, 7 Jul 2005, Yukihiro Matsumoto wrote:

Hi,

In message "Re: Inheriting Array and slice() behaviour" > on Wed, 6 Jul 2005 18:17:21 +0900, Sylvain Joyeux <sylvain.joyeux@polytechnique.org> writes:

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

--

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

===============================================================================

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

Excerpts from Yukihiro Matsumoto's mail of 6 Jul 2005 (EDT):

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

···

on Wed, 6 Jul 2005 18:17:21 +0900, Sylvain Joyeux > <sylvain.joyeux@polytechnique.org> writes:

--
William <wmorgan-ruby-talk@masanjin.net>

Hi --

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

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

···

On Thu, 7 Jul 2005, Sylvain Joyeux wrote:

--
David A. Black
dblack@wobblini.net

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

> 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

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

···

--
William <wmorgan-ruby-talk@masanjin.net>

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

···

On Thu, 7 Jul 2005, Sylvain Joyeux wrote:

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

--

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

===============================================================================

Hi,

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.

···

In message "Re: Inheriting Array and slice() behaviour" on Thu, 7 Jul 2005 07:54:17 +0900, William Morgan <wmorgan-ruby-talk@masanjin.net> writes:

Hi,

···

In message "Re: Inheriting Array and slice() behaviour" on Thu, 7 Jul 2005 03:14:08 +0900, "Ara.T.Howard" <Ara.T.Howard@noaa.gov> writes:

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.

Hi,

···

In message "Re: Inheriting Array and slice() behaviour" on Thu, 7 Jul 2005 05:02:25 +0900, Sylvain Joyeux <sylvain.joyeux@polytechnique.org> writes:

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.

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

···

On Thu, 7 Jul 2005, Sylvain Joyeux wrote:

--
David A. Black
dblack@wobblini.net

I disagree. In an OO language, a slice of an Expression should be an
Expression if Expression is a descendant of Array.

-austin

···

On 7/6/05, Sylvain Joyeux <sylvain.joyeux@polytechnique.org> wrote:

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

--
Austin Ziegler * halostatue@gmail.com
               * Alternate: austin@halostatue.ca

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

···

William Morgan <wmorgan-ruby-talk@masanjin.net> wrote:

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

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

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

Excerpts from Yukihiro Matsumoto's mail of 6 Jul 2005 (EDT):

>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?

But I don't think it can be. To be initialized, #initialize must be
called, but #initialize can have required arguments. That's the essence
of my trouble with Array#slice: returned objects can't be initialized
properly.

I think the subclass should override #slice if they want this behavior,
and then they can explicitly call #new with the correct arguments.
They're probably going to have to do this for #+, anyways.

But I guess I'm the lone gunman by now, so I will stop pressing the
issue. Thanks for your comments!

···

--
William <wmorgan-ruby-talk@masanjin.net>

I disagree. In an OO language, a slice of an Expression should be an
Expression if Expression is a descendant of Array.

I don't. We're on the side of dogmas here, so no need to argue about it.

Sylvain

···

--
Sylvain Joyeux

Excerpts from Austin Ziegler's mail of 7 Jul 2005 (EDT):

In an OO language, a slice of an Expression should be an Expression if
Expression is a descendant of Array.

I'd say that's probably a requirement for a well-written OO Expression
*class*, depending on what exactly it's supposed to be doing. The
question is whether the language should---or even can---enforce that.

In particular, what if Expression#initialize takes parameters that, for
sub-expressions, need to be determined from some combination of the
receiver's state and the arguments to #slice? That's the situation here,
and it needs to be explicitly handled by the author. Anything Ruby does
automatically will be wrong.

···

--
William <wmorgan-ruby-talk@masanjin.net>