Canonical Array#append to hang hook

I have a class which inherit from Array. I want to perform some task
each time something is append to an instance of this class. The problem
is: there are so many way to append something to an array. For example:

class Narray < Array
  alias_method :old_push, :push
  def push(*obj)
    p "pushing #{obj}"
    old_push(*obj)
  end
  alias_method :old_append, :<<
  def <<(obj)
    p "<<ing #{obj}"
    old_append(obj)
  end
  alias_method :old_bracket, :[]=
  def []=(i,l=0)
    p "[]=ing #{i}"
    old_bracket(i,l)
  end
  alias_method :old_add, :+
  def +(i)
    p "+ing #{i}"
    old_add(i)
  end
end

a = Narray.new
a.push(1)
a << 2
a[a.length] = 3
a = a + [4]
p a

I forgot Array#insert, Array#fill and probably other...

Is there any Array#append function that can hooked to perform task every
time something is append to a?

Any solution is welcome.

LarsTico

PS: sorry for my poor frenchy english, but I hope you understand me.

···

--
Posted via http://www.ruby-forum.com/.

I'd probably consider not using inheritance but wrapping your Array in
some other class. That way you can expose the interface you want to
expose and esp. limit the way to modify the array.

Kind regards

robert

···

2007/9/30, Rassat Nicolas <nicolas.rassat@free.fr>:

I have a class which inherit from Array. I want to perform some task
each time something is append to an instance of this class. The problem
is: there are so many way to append something to an array. For example:

class Narray < Array
  alias_method :old_push, :push
  def push(*obj)
    p "pushing #{obj}"
    old_push(*obj)
  end
  alias_method :old_append, :<<
  def <<(obj)
    p "<<ing #{obj}"
    old_append(obj)
  end
  alias_method :old_bracket, :=
  def =(i,l=0)
    p "=ing #{i}"
    old_bracket(i,l)
  end
  alias_method :old_add, :+
  def +(i)
    p "+ing #{i}"
    old_add(i)
  end
end

a = Narray.new
a.push(1)
a << 2
a[a.length] = 3
a = a + [4]
p a

I forgot Array#insert, Array#fill and probably other...

Is there any Array#append function that can hooked to perform task every
time something is append to a?

Any solution is welcome.

Rassat Nicolas wrote:

I have a class which inherit from Array. I want to perform some task
each time something is append to an instance of this class. The problem
is: there are so many way to append something to an array. For example:

class Narray < Array

...

  alias_method :old_add, :+
  def +(i)
    p "+ing #{i}"
    old_add(i)
  end
end

It's not clear to me (though maybe it is clear to you) whether + should be considered as a kind of append operation. Note that + doesn't modify the array, but returns a new array. Furthermore, + doesn't return an instance of NArray, but just an Array:

irb(main):001:0> class A < Array
irb(main):002:1> def +(x); super; end
irb(main):003:1> end
=> nil
irb(main):004:0> a = A.new
=>
irb(main):005:0> (a + ).class
=> Array

However:

irb(main):008:0> (a.concat ).class
=> A

···

--
        vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

class Narray
  def initialize(a)
     @array = a.dup.freeze
  end

  def method_missing(name, *args, &block)
        @array.send(name, *args, &block)
     rescue NoMethodError
        super
     rescue TypeError => e
        if e.message =~ /can't modify frozen array/
          a = @array.dup
          p "#{name}ing"
          r = a.send(name, *args, &block)
          @array = a.freeze
          r
       else
         raise e
       end
  end

  def inspect
     @array.inspect
  end

  def to_s
     @array.to_s
  end
end

narray = Narray.new [1,2,3]
narray.append 4
p narray
narray << 3
p narray
narray.fill(2)
p narray

Totally didn't test this

···

On 9/30/07, Rassat Nicolas <nicolas.rassat@free.fr> wrote:

I have a class which inherit from Array. I want to perform some task
each time something is append to an instance of this class. The problem
is: there are so many way to append something to an array. For example:

Answer to the three responses:

To Robert Klemme:

Sure I can do this but this way I lose every benefit of inheritance! And
I'll have to rewrite "accesor" function to access function to the
instance variable array. That is not very DRY.

To Joel VanderWerf:

Your remark about Array#+ is completly right. I just put it here to test
if Array#push or Array#<< were implementing using the Array#+ function.

To Logan Capaldo:

I got this error:
narray.rb:9:in `method_missing': undefined method `append' for [1,
2,3]:Narray (NoMethodError)
        from narray.rb:32

But this is not the point. This way I'll catch every func, not just func
that insert something on the array. I would have to do something like my
example. Again, this is not really DRY.

Does anyone knows how insertion is implementing in the ruby core code or
should I dig the code by myself? This time is about inserting into an
array, but it can be over action; would it not be great to have some
hook function to solve easily this kind of problem?

To all you three, thanks for your answer

···

--
Posted via http://www.ruby-forum.com/.

No you don't. #method_missing and #respond_to? are your friends. Or
you can use Delegator.

  class NRArray
    def initialize(*args, &block)
      @nrarray = if args.empty? and block.nil?
                   
                 elsif args.empty?
                   Array.new &block
                 else
                   Array.new(*args, &block)
                 end
    end

    def respond_to?(sym)
      ok = super
      ok = @nrarray.respond_to?(sym) unless ok
      ok
    end

    def method_missing(sym, *args, &block)
      puts "#{sym}ing #{args.inspect}"
      @nrarray.send(sym, *args, &block)
    end
  end

(Note that NArray is a well-known numerical array extension, so Narray
is likely to be confusing if you need it.)

-austin

···

On 10/2/07, Lars Ticot <nicolas.rassat@free.fr> wrote:

To Robert Klemme:
Sure I can do this but this way I lose every benefit of inheritance! And
I'll have to rewrite "accesor" function to access function to the
instance variable array. That is not very DRY.

--
Austin Ziegler * halostatue@gmail.com * http://www.halostatue.ca/
               * austin@halostatue.ca * You are in a maze of twisty little passages, all alike. // halo • statue
               * austin@zieglers.ca

Actually the only real difference is object identity IMHO. As has been
demonstrated there are easy to use tools that make delegation of
method invocations to the real Array really simple.

If you provide more insight into your use case then maybe even more /
better / easier solutions will come up. I am convinced that it is a
bad idea to inherit Array most of the time similarly to it being a bad
idea to inherit java.util.HashMap.

Kind regards

robert

···

2007/10/2, Lars Ticot <nicolas.rassat@free.fr>:

To Robert Klemme:

Sure I can do this but this way I lose every benefit of inheritance! And
I'll have to rewrite "accesor" function to access function to the
instance variable array. That is not very DRY.

Heh I'm silly. There actually is no Array#append method so that's what
should have happend. Delete the narray.append line and then try it
out, I think you'll find it's a pleasantly evil hack.

···

On 10/2/07, Lars Ticot <nicolas.rassat@free.fr> wrote:

To Logan Capaldo:

I got this error:
narray.rb:9:in `method_missing': undefined method `append' for [1,
2,3]:Narray (NoMethodError)
       from narray.rb:32

Austin Ziegler wrote:

To Robert Klemme:
Sure I can do this but this way I lose every benefit of inheritance! And
I'll have to rewrite "accesor" function to access function to the
instance variable array. That is not very DRY.

No you don't. #method_missing and #respond_to? are your friends. Or
you can use Delegator.

yes but:

a = NRArray.new
a.push(2) # => "pushing [2]"
a.pop # => "poping "

I don't want to catch the poping!

The delegator is an answer, like having an Array instance var. I lose
the inheritance benefit.

It would have been great if, Array#push, Array#<< and Array#insert,
would used the same Array#internal_insert in their implementation. So
hooking this one would give me a hook on the other one. Basicaly they
are all three doing insertion!

(Note that NArray is a well-known numerical array extension, so Narray
is likely to be confusing if you need it.)

Right! It was just the first name that came to me (N(ew)Array) when I
start thinking at this problem :wink:

···

On 10/2/07, Lars Ticot <nicolas.rassat@free.fr> wrote:

--
Posted via http://www.ruby-forum.com/\.

Robert Klemme wrote:

To Robert Klemme:

Sure I can do this but this way I lose every benefit of inheritance! And
I'll have to rewrite "accesor" function to access function to the
instance variable array. That is not very DRY.

Actually the only real difference is object identity IMHO.

Right for me

As has been
demonstrated there are easy to use tools that make delegation of
method invocations to the real Array really simple.

Right too. But they induced over cost (during coding time and execution
time)

If you provide more insight into your use case then maybe even more /
better / easier solutions will come up.

It's a work at really pre alpha stage (actually I am just starting to
think about how to do things). It's a kind of spreadsheet that works by
column. That should approximately look like

class Cell
  # represent the content of a cell
  # define some useful functions: format, is_number?,....
end

class Column
  # represent the column as an array of Cell.
end

I am convinced that it is a
bad idea to inherit Array most of the time similarly to it being a bad
idea to inherit java.util.HashMap.

To continue on my example, I want to check if cells that I add on my
column are number (just an example). Two possibilities:
- if Column inherit from Array I'll have to do something like in my
first example.
- if Column does not inherit from Array it will have to delegate Array's
function that I need (almost all function in Array)and the delegation
has a cost (see example at the end). Moreover, what if I want to use a
lib that don't rely only on duck typing and use some crapy things like
this?
def do_something(obj)
if obj.class == Array then
  ...
else
  ...
end
So the identity of object *is* important in some (bad?) situation. As an
example, Gtk lib (and probably others) does such things (I'll post
something on this and multiple inheritance in some time).

My column *is* an array. So I think it's pretty clear that it should be
derived from Array.

Thanks

Here is a quick and dirty test I made to see cost of delegating.
Suprisingly, delegating and forwarding are really slow. As slow as using
the method_missing (without error test). I would expect them to be as
quick as the "indirect" (class C) method.

require 'delegate'
require 'forwardable'
require 'benchmark'
include Benchmark

class A
  def hello
  end
end

class B < A
end

class C
  def initialize
    @a = A.new
  end
  def hello
    @a.hello
  end
end

class D < DelegateClass(A)
  def initialize
    a = A.new
    super(a)
  end
end

class E
  extend Forwardable
  def_delegator(:@a, :hello, :hello)
  def initialize
    @a=A.new
  end
end

class F
  def initialize
    @a=A.new
  end
  def method_missing(name, *args, &block)
    @a.send(name, *args, &block)
  end
end

a = A.new
b = B.new
c = C.new
d = D.new
e = E.new
f = F.new

m=100000 # My computer is an old k6-400 so you probably have to increase
this!

bm(10) do |x|
  x.report("direct ") { m.times{a.hello } }
  x.report("inherit ") { m.times{b.hello } }
  x.report("indirect") { m.times{c.hello } }
  x.report("delegate") { m.times{d.hello } }
  x.report("forward ") { m.times{e.hello } }
  x.report("missing ") { m.times{f.hello } }
end

···

2007/10/2, Lars Ticot <nicolas.rassat@free.fr>:

--
Posted via http://www.ruby-forum.com/\.