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.

2007/9/30, Rassat N. [email protected]:

alias_method :old_append, :<<
def +(i)
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.

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

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

On 9/30/07, Rassat N. [email protected] 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
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

Answer to the three responses:

To Robert K.:

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

I got this error:
narray.rb:9:in method_missing': undefined methodappend’ 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

Austin Z. wrote:

On 10/2/07, Lars T. [email protected] wrote:

To Robert K.:
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 T. [email protected] wrote:

To Robert K.:
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.

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 T. [email protected] wrote:

To Logan C.:

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

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.

2007/10/2, Lars T. [email protected]:

To Robert K.:

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

Robert K. wrote:

2007/10/2, Lars T. [email protected]:

To Robert K.:

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

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